Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wedge support #48

Open
mottosso opened this issue Mar 14, 2016 · 9 comments
Open

Wedge support #48

mottosso opened this issue Mar 14, 2016 · 9 comments
Assignees
Labels

Comments

@mottosso
Copy link
Member

Goal

Enable capturing of wedges.

What is a "wedge"? In Houdini, a wedge is defined as running an operation multiple times, each time varying some parameter(s). It can be useful when for example working with simulations and you are interested in the effects of a series of configurations to determine which works best. - Reference

Usecase

You are tasked with producing playblasts for a combination of properties of nCloth.

  • Stiff versus Soft
  • Compressable versus Non-compressable
  • Heavy versus Light

In this example, each feature has two states - on or off - resulting in 3**2=9 wedges.

Taking in mind that it may be important to return back to a particular configuration, given a successful review, a traditional workflow would have required an artist to alter the settings of a scene, save this as a uniquely rememberable version, playblast this, and then do the same for the other 8 variations.

With capture.wedge the same is both more manageable and with better performance, given that each capture can occur simultaneously (see below about multi-processing).

Interface

Each wedge is based on an animation layer. Animation layers are capable of storing independent configurations that may be either blended or used in isolation.

  1. Setup an animation layer for each configuration

  2. Run capture.wedge

    import capture
    capture.wedge(["layer1", "littleSmoke", "moreSmoke", "fastMotion;moreSmoke"])

Implementation

An animation layer is used per wedge and each wedge is set off as a background process, using a copy of the scene at its current state, including camera and scene settings (using capture.parse_view).

multiprocessing

An option is provided for running Maya sessions as a background process simultaneously, in addition to the default behaviour of running each capture successively one after the other.

import capture
capture.wedge(["compressible", "nonCompressible"], multiprocess=True)

The benefit of the former is simultaneous capturing, resulting in an n-times shorter capturing duration, assuming capturing consumes a single processor core each. For example, 8 wedges involving nCloth runs 8 times as fast as running 8 captures within the same Maya session.

on_finished

With multi-processing, it's impossible to retrieve the resulting files created during capturing as the process is asynchronous. Therefore, there is an option to pass a callback for when it finishes, which is then passed a list of each newly created capture.

import capture
import subprocess

def run_in_rv(files):
  subprocess.Popen(["rv"] + files)

capture.wedge(["compressible", "nonCompressible"],
              multiprocess=True,
              on_finished=run_in_rv)

The above results in the finished captures automatically opening in RV when finished.

combinations

Animation layers provide a native ability to blend between each other, enabling a combination off effects.

Consider the following three layers.

  • Stiff
  • Compressible
  • Heavy

Layers can then be activated two-and-two.

# Compressible and heavy
stiff: 0
compressible: 1
heavy: 1

# Stiff and heavy
stiff: 1
compressible: 0
heavy: 1

# Stiff and compressible
stiff: 1
compressible: 1
heavy: 0

...
import capture
import subprocess

def run_in_rv(files):
  subprocess.Popen(["rv"] + files)

capture.wedge(["stiff;compressible", "stiff;heavy", "compressible;heavy"],
              multiprocess=True,
              on_finished=run_in_rv)
@mottosso mottosso self-assigned this Mar 14, 2016
@BigRoy
Copy link
Collaborator

BigRoy commented Mar 14, 2016

This sounds great!

I think it's even better if this would be abstracted even further. Say the capture command takes a context parameter that is a list of context managers.

Then performing a wedge like this becomes easy:

# pseudo
for layer in ["stiff", "heavy"]:
    capture (context=anim _ layer(layer))

This would even allow other artists to instead of capturing different anim layers to do so for different render layers making it possible to do previews with different textures.

I feel the animation layer technique is somewhat specific and can easily be abstracted into an own custom context manager.

@mottosso
Copy link
Member Author

Thanks, glad you like it.

But I'm not exactly following, what is this "context" and how would it work capturing in a loop? How would you be able to use the output of each run? And what is anim_layer()?

@BigRoy
Copy link
Collaborator

BigRoy commented Mar 14, 2016

I think the creation of multiple output is a different issue. But the context manager I referred to was a means to 'plugging in' a custom "pre-capture" and "post-capture" functionality as it runs through the playblast.

It's like adding a pre_callback and post_callback and ensuring the post callbacks are triggered. All it would do is temporarily set the state of the animation layers. So in your case it would be something like

# pseudocode
import contextlib

@contextlib.contextmanager
def wedge(layers_str):
    layers = cmds.ls(type="animLayer")
    enable = layers_str.split(";")

    original = {}
    for layer in layers:
        original[layer] = get_state(layer)
        set_state(layer, layer in enable)

    try:
        yield
    finally:
        for layer, state in original.items():
            set_state(layer, state)

for state in ["stiff", "loose", "stiff;high_substep"]:
    capture(context=wedge(state))

I think combining multiple captures into a single "view" result like on_finished is useful by itself, even without using a wedge. Maybe even captures with multiple cameras.

# pseudo

def run_in_rv(files):
  subprocess.Popen(["rv"] + files)

with combined_view(on_finished=run_in_rv) as view:
    result = capture(view=False)
    view.append(result)

As stated, likely better fitted for a separate issue.

@mottosso
Copy link
Member Author

Ah. Ok. I implemented this briefly here, where I also made a context manager like this, but didn't think to expose it publicly, it was only used within wedge().

@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)

Have a look at how it's used, to see if your idea could work in practice. Are you thinking it might be an idea to expose this context manager for manual use and calling capture() as usual?

@mottosso
Copy link
Member Author

PR here.

@mottosso
Copy link
Member Author

  • Make multiprocess=True into async=True, shorter, sweeter
  • Make __post to __monitor, as it better describes what it does.

@BigRoy
Copy link
Collaborator

BigRoy commented Mar 15, 2016

Oof. Yeah,... so separate out the context manager so one could use their own custom ones to perform wedges in different manners as described earlier.

Use the anim layer thing solely to show an example. The checking for missing anim layers and alike should also become part of the method that you pass to wedge since it's related to the anim layer technique.

@mottosso
Copy link
Member Author

Ok, I think it sounds like we're talking about two things.

  • How to wedge
  • How to capture wedges

Are you also talking about how to capture wedges, or about how to wedge in the first place?

@mottosso
Copy link
Member Author

For the wedge interface, we'll need

  • progress bars, per process, based on current frame
  • kill individual
  • kill all
  • name per process, based on layer
 _______________________
|______________________x|
|                       |
| layer1 |------o---| x |
| layer2 |----o-----| x |
| layer3 |-----o----| x |
|          ____________ |
|         |            ||
|         | Stop all x ||
|         |____________||
|_______________________|

Some more core functionality.

  • queues, with a limited set of processes running at once, based on parmeter or..
  • monitor memory use versus memory required.

For example, if the currently running process consumes 1gb, and the machine currently has 10gb available, then a maximum of 9 simultaneous processes can run. Taking into account that we would want to maintain at least some ram, perhaps 1gb, that gives a maximum of 8 running processes.

We'll do a pre-flight check to test whether there is enough ram to run a single process, otherwise revert to running locally. If the test passes, then we'll run the maximum amount of processes based on available memory and save the rest in a queue to run once a process has finished.

The callback is only called when everything has finished.

We might also need:

  • on_each_finished
  • on_each_failed
  • on_failed
  • on_finished

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants