From e2fddb829ee13aa80d5d8f2e33681f0ab5fa5d2c Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Mon, 14 Mar 2016 18:02:07 +0000 Subject: [PATCH 1/4] Initial commit of wedge feature --- capture.py | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) diff --git a/capture.py b/capture.py index 2a23ee5..f897d03 100644 --- a/capture.py +++ b/capture.py @@ -4,8 +4,14 @@ """ +import os import re import sys +import json +import shutil +import tempfile +import threading +import subprocess import contextlib from maya import cmds @@ -211,6 +217,127 @@ def replace(m): return output +def wedge(layers, + multiprocess=False, + on_finished=None, + silent=True, + **kwargs): + """Capture from a camera once per animation layer + + Use this to create wedges of varying settings. + + Arguments: + layers (list): Layers, or combinations of layers, to use per capture + multiprocess (bool): Whether to run linearly in your current scene or + simultaneously in the background. + on_finished (callable): Callback for when multiprocess the entire + operation is finished (only relevant with `multiprocess`). + Outputted files are passed to callback as a list of absolute paths. + + """ + + missing = [l for l in layers if not cmds.objExists(l)] + unmuted = [l for l in layers if not cmds.animLayer( + l, query=True, mute=True)] + + if missing: + raise ValueError("These animation layers was not found: %s" % missing) + + if unmuted: + raise ValueError("These animation layers were not muted: %s" % unmuted) + + if not multiprocess: + output = list() + + for layer in layers: + with _solo_animation_layer(layer): + output.append(capture(**kwargs)) + + return output + + else: + processes = list() + tempdir = tempfile.mkdtemp() + + def __post(): + """Threaded callback""" + output = list() + cmds.warning("Running post-operation..") + for process in processes: + fname = None + + # Listen for file output + for line in iter(process.stdout.readline, b""): + if not silent: + sys.stdout.write(line) + + if "__maya_capture_output" in line: + fname = line.split("__maya_capture_output: ")[-1] + + if "__maya_capture_output" in line: + print(line) + + if fname is None: + sys.stderr.write("Process did not output capture output.") + + output.append(fname) + + cmds.warning("Done, cleaning up temporary files..") + shutil.rmtree(tempdir) + + # Trigger callback + if on_finished is not None: + cmds.warning("Running callback..") + on_finished(output) + + # Export scene + cmds.warning("Saving scene..") + scene = os.path.join(tempdir, "temp.mb") + cmds.file(scene, exportAll=True, type="mayaBinary") + + preset = parse_active_scene() + if "camera" in kwargs: + preset.update(parse_view(kwargs["camera"])) + else: + preset.update(parse_active_view()) + preset = json.dumps(preset, indent=4) + + cmds.warning("Running wedges in background..") + + for layer in layers: + script = """ +print("Within subprocess..") +import sys +sys.path[:] = {paths} + +from maya import cmds, standalone +standalone.initialize() + +cmds.file("{scene}", open=True, force=True) + +import capture +output = capture.wedge(["{layer}"], **{preset}) +print("__maya_capture_output: %s" % output) +""".format(layer=layer, scene=scene, preset=preset, paths=json.dumps(sys.path)) + + print("Running script: %s" % script) + + scriptpath = os.path.join(tempdir, layer + ".py") + with open(scriptpath, "w") as f: + f.write(script) + + popen = subprocess.Popen("mayapy %s" % scriptpath, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True) + + # Track process + processes.append(popen) + + cmds.warning("Awaiting background processes to finish..") + threading.Thread(target=__post).start() + + CameraOptions = { "displayGateMask": False, @@ -469,6 +596,19 @@ def apply_scene(**options): floatValue=["playblastQuality", options["quality"]]) +@contextlib.contextmanager +def _solo_animation_layer(layer): + """Isolate animation layer""" + if not cmds.animLayer(layer, query=True, mute=True): + raise ValueError("%s must be muted" % layer) + + try: + cmds.animLayer(layer, edit=True, mute=False) + yield + finally: + cmds.animLayer(layer, edit=True, mute=True) + + @contextlib.contextmanager def _applied_view(panel, **options): """Apply options to panel""" From 8963b83117fca6adf08be8ddbe9d9839130a8213 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Tue, 15 Mar 2016 16:57:37 +0000 Subject: [PATCH 2/4] Almost working version --- capture.py | 105 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 34 deletions(-) diff --git a/capture.py b/capture.py index f897d03..187fe61 100644 --- a/capture.py +++ b/capture.py @@ -217,8 +217,10 @@ def replace(m): return output + + def wedge(layers, - multiprocess=False, + async=False, on_finished=None, silent=True, **kwargs): @@ -228,8 +230,8 @@ def wedge(layers, Arguments: layers (list): Layers, or combinations of layers, to use per capture - multiprocess (bool): Whether to run linearly in your current scene or - simultaneously in the background. + async (bool): Whether to run asynchronously, or one at a time + silent (bool): Whether or not to print output of subprocesses on_finished (callable): Callback for when multiprocess the entire operation is finished (only relevant with `multiprocess`). Outputted files are passed to callback as a list of absolute paths. @@ -237,21 +239,20 @@ def wedge(layers, """ missing = [l for l in layers if not cmds.objExists(l)] - unmuted = [l for l in layers if not cmds.animLayer( - l, query=True, mute=True)] if missing: raise ValueError("These animation layers was not found: %s" % missing) - if unmuted: - raise ValueError("These animation layers were not muted: %s" % unmuted) - - if not multiprocess: + if not async: + # Keep it simple output = list() + with _muted_animation_layers(layers): + for layer in layers: + with _solo_animation_layer(layer): + output.append(capture(**kwargs)) - for layer in layers: - with _solo_animation_layer(layer): - output.append(capture(**kwargs)) + if on_finished is not None: + on_finished(output) return output @@ -259,7 +260,7 @@ def wedge(layers, processes = list() tempdir = tempfile.mkdtemp() - def __post(): + def __monitor(): """Threaded callback""" output = list() cmds.warning("Running post-operation..") @@ -271,16 +272,20 @@ def __post(): if not silent: sys.stdout.write(line) - if "__maya_capture_output" in line: - fname = line.split("__maya_capture_output: ")[-1] + if line.startswith("out: "): + sys.stdout.write(line[5:]) + # Keep an eye out for when the output is + # being printed. if "__maya_capture_output" in line: - print(line) + fname = line.split("__maya_capture_output: ")[-1] + fname = fname.strip() # Remove newline if fname is None: - sys.stderr.write("Process did not output capture output.") - - output.append(fname) + sys.stderr.write( + "Process did not output capture output.\n") + else: + output.append(fname) # remove newline at end cmds.warning("Done, cleaning up temporary files..") shutil.rmtree(tempdir) @@ -295,37 +300,56 @@ def __post(): scene = os.path.join(tempdir, "temp.mb") cmds.file(scene, exportAll=True, type="mayaBinary") - preset = parse_active_scene() - if "camera" in kwargs: - preset.update(parse_view(kwargs["camera"])) - else: - preset.update(parse_active_view()) - preset = json.dumps(preset, indent=4) - cmds.warning("Running wedges in background..") + preset = parse_active_view() + # print(json.dumps(preset, indent=4)) + preset.update(parse_active_scene()) + preset.update(kwargs) + for layer in layers: script = """ -print("Within subprocess..") +print("out: Within subprocess..") +import os import sys -sys.path[:] = {paths} +import json from maya import cmds, standalone standalone.initialize() +scene = \"{scene}\" +layer = \"{layer}\" +preset = json.loads('{preset}') +print("out: %s" % json.dumps(preset, indent=4)) + +print("out: Opening %s" % scene) cmds.file("{scene}", open=True, force=True) import capture -output = capture.wedge(["{layer}"], **{preset}) -print("__maya_capture_output: %s" % output) -""".format(layer=layer, scene=scene, preset=preset, paths=json.dumps(sys.path)) - print("Running script: %s" % script) +# One file per layer +preset["filename"] = layer +preset["offscreen"] = True + +output = capture.wedge([layer], **preset) +print("out: Made it past capture..") +print("__maya_capture_output: %s" % output[0]) + +# Safely exit without throwing an exception +sys.exit() +""" + script = script.format( + layer=layer, + scene=scene, + preset=json.dumps(kwargs), + paths=json.dumps(sys.path) + ) scriptpath = os.path.join(tempdir, layer + ".py") with open(scriptpath, "w") as f: f.write(script) + # print("Running script: %s" % script) popen = subprocess.Popen("mayapy %s" % scriptpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -335,8 +359,7 @@ def __post(): processes.append(popen) cmds.warning("Awaiting background processes to finish..") - threading.Thread(target=__post).start() - + threading.Thread(target=__monitor).start() CameraOptions = { @@ -609,6 +632,20 @@ def _solo_animation_layer(layer): cmds.animLayer(layer, edit=True, mute=True) +@contextlib.contextmanager +def _muted_animation_layers(layers): + state = dict((layer, cmds.animLayer(layer, query=True, mute=True)) + for layer in layers) + try: + for layer in layers: + cmds.animLayer(layer, edit=True, mute=True) + yield + + finally: + for layer, muted in state.items(): + cmds.animLayer(layer, edit=True, mute=muted) + + @contextlib.contextmanager def _applied_view(panel, **options): """Apply options to panel""" From af065c228c78cd7c5898b67e7e5dc23044f964c1 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Wed, 16 Mar 2016 08:03:23 +0000 Subject: [PATCH 3/4] PEP08 and notes --- capture.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/capture.py b/capture.py index 187fe61..33ab812 100644 --- a/capture.py +++ b/capture.py @@ -26,6 +26,7 @@ def capture(camera=None, width=None, height=None, filename=None, + complete_filename=None, start_frame=None, end_frame=None, frame=None, @@ -41,8 +42,7 @@ def capture(camera=None, camera_options=None, display_options=None, viewport_options=None, - viewport2_options=None, - complete_filename=None): + viewport2_options=None): """Playblast in an independent panel Arguments: @@ -51,6 +51,8 @@ def capture(camera=None, height (int, optional): Height of output in pixels filename (str, optional): Name of output file. If none is specified, no files are saved. + complete_filename (str, optional): Exact name of output file. Use this + to override the output of `filename` so it excludes frame padding. 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. @@ -78,8 +80,6 @@ def capture(camera=None, 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 @@ -218,7 +218,6 @@ def replace(m): return output - def wedge(layers, async=False, on_finished=None, @@ -243,6 +242,9 @@ def wedge(layers, if missing: raise ValueError("These animation layers was not found: %s" % missing) + # Do not show player for each finished capture + # kwargs["viewer"] = False + if not async: # Keep it simple output = list() @@ -273,7 +275,7 @@ def __monitor(): sys.stdout.write(line) if line.startswith("out: "): - sys.stdout.write(line[5:]) + print(line[5:]) # Keep an eye out for when the output is # being printed. @@ -303,7 +305,6 @@ def __monitor(): cmds.warning("Running wedges in background..") preset = parse_active_view() - # print(json.dumps(preset, indent=4)) preset.update(parse_active_scene()) preset.update(kwargs) @@ -320,7 +321,7 @@ def __monitor(): scene = \"{scene}\" layer = \"{layer}\" preset = json.loads('{preset}') -print("out: %s" % json.dumps(preset, indent=4)) +print("out: JSON: %s" % json.dumps(preset, indent=4)) print("out: Opening %s" % scene) cmds.file("{scene}", open=True, force=True) @@ -350,7 +351,7 @@ def __monitor(): f.write(script) # print("Running script: %s" % script) - popen = subprocess.Popen("mayapy %s" % scriptpath, + popen = subprocess.Popen("mayapy -u %s" % scriptpath, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True) From 677dbd6806d70a8aecce6890e12c9c3f5476c743 Mon Sep 17 00:00:00 2001 From: Marcus Ottosson Date: Fri, 18 Mar 2016 08:04:23 +0000 Subject: [PATCH 4/4] Asynchronous logging support, and fixed bug in mayapy playblasting and viewport2. --- capture.py | 74 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 29 deletions(-) diff --git a/capture.py b/capture.py index 33ab812..1fca57a 100644 --- a/capture.py +++ b/capture.py @@ -304,37 +304,41 @@ def __monitor(): cmds.warning("Running wedges in background..") - preset = parse_active_view() - preset.update(parse_active_scene()) - preset.update(kwargs) - for layer in layers: script = """ -print("out: Within subprocess..") import os import sys import json +import logging + +log = logging.getLogger() +log.info("out: Within subprocess..") from maya import cmds, standalone standalone.initialize() -scene = \"{scene}\" +assert cmds.objExists("persp") + +scene = r\"{scene}\" layer = \"{layer}\" preset = json.loads('{preset}') -print("out: JSON: %s" % json.dumps(preset, indent=4)) +log.info("out: JSON: %s" % json.dumps(preset, indent=4)) -print("out: Opening %s" % scene) -cmds.file("{scene}", open=True, force=True) +log.info("out: Opening %s" % scene) +cmds.file(scene, open=True, force=True) import capture # One file per layer preset["filename"] = layer -preset["offscreen"] = True +preset["off_screen"] = True +preset["camera"] = "persp" output = capture.wedge([layer], **preset) -print("out: Made it past capture..") -print("__maya_capture_output: %s" % output[0]) +# output = cmds.playblast() +# output = capture.capture() +log.info("out: Made it past capture..") +log.info("__maya_capture_output: %s" % output[0]) # Safely exit without throwing an exception sys.exit() @@ -346,6 +350,7 @@ def __monitor(): paths=json.dumps(sys.path) ) + print("Running script: %s" % script) scriptpath = os.path.join(tempdir, layer + ".py") with open(scriptpath, "w") as f: f.write(script) @@ -436,7 +441,7 @@ def __monitor(): "enableTextureMaxRes": False, "bumpBakeResolution": 64, "colorBakeResolution": 64, - "floatingPointRTEnable": True, + "floatingPointRTEnable": False, "floatingPointRTFormat": 1, "gammaCorrectionEnable": False, "gammaValue": 2.2, @@ -726,7 +731,7 @@ def _applied_camera_options(options, panel): old_options[opt] = cmds.getAttr(camera + "." + opt) except: sys.stderr.write("Could not get camera attribute " - "for capture: %s" % opt) + "for capture: %s\n" % opt) options.pop(opt) for opt, value in options.iteritems(): @@ -896,18 +901,29 @@ def _in_standalone(): # # -------------------------------- -version = cmds.about(version=True) -if "2016" in version: - 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, - }) +if _in_standalone(): + # This setting doesn't appear to work in mayapy. + # Tested in Linux Scientific 6 and Windows 8, + # Nvidia Quadro and GeForce 650m + Viewport2Options["floatingPointRTEnable"] = False + +try: + version = cmds.about(version=True) + if "2016" in version: + 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, + }) +except: + # about might not exist in mayapy + # if not first initialized. + pass