This commit is contained in:
Jeffreytsai1004 2025-01-14 03:03:51 +08:00
parent b70480685d
commit 908f9b94af
28 changed files with 1848 additions and 0 deletions

View File

@ -0,0 +1,310 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &mdash; Pose Wrangler 2.0.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="#" />
<link rel="search" title="Search" href="search.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Pose Wrangler
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="overview.html">2.0.0 Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="upgrading.html">Upgrading to 2.0.0</a></li>
<li class="toctree-l1"><a class="reference internal" href="mirror_mapping.html">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Pose Wrangler</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Index</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
<a href="#A"><strong>A</strong></a>
| <a href="#C"><strong>C</strong></a>
| <a href="#D"><strong>D</strong></a>
| <a href="#E"><strong>E</strong></a>
| <a href="#G"><strong>G</strong></a>
| <a href="#I"><strong>I</strong></a>
| <a href="#L"><strong>L</strong></a>
| <a href="#M"><strong>M</strong></a>
| <a href="#O"><strong>O</strong></a>
| <a href="#P"><strong>P</strong></a>
| <a href="#R"><strong>R</strong></a>
| <a href="#S"><strong>S</strong></a>
| <a href="#U"><strong>U</strong></a>
| <a href="#V"><strong>V</strong></a>
</div>
<h2 id="A">A</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.add_blendshape">add_blendshape() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.add_driven_transforms">add_driven_transforms() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.add_drivers">add_drivers() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="extensions.html#pose_wrangler.v2.model.base_extension.PoseWranglerExtension.api">api (pose_wrangler.v2.model.base_extension.PoseWranglerExtension property)</a>
</li>
</ul></td>
</tr></table>
<h2 id="C">C</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.create_blendshape">create_blendshape() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.create_pose">create_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.create_rbf_solver">create_rbf_solver() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.current_solver">current_solver (pose_wrangler.v2.main.UERBFAPI property)</a>
</li>
</ul></td>
</tr></table>
<h2 id="D">D</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.delete_blendshape">delete_blendshape() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.delete_pose">delete_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.delete_rbf_solver">delete_rbf_solver() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.deserialize">deserialize() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.deserialize_from_file">deserialize_from_file() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="E">E</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.edit_blendshape">edit_blendshape() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.edit_solver">edit_solver() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="extensions.html#pose_wrangler.v2.model.base_extension.PoseWranglerExtension.execute">execute() (pose_wrangler.v2.model.base_extension.PoseWranglerExtension method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.extensions">extensions (pose_wrangler.v2.main.UERBFAPI property)</a>
</li>
</ul></td>
</tr></table>
<h2 id="G">G</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.get_context">get_context() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.get_extension_by_type">get_extension_by_type() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.get_rbf_solver_by_name">get_rbf_solver_by_name() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.get_solver_edit_status">get_solver_edit_status() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.get_ui_context">get_ui_context() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.go_to_pose">go_to_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="I">I</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.isolate_blendshape">isolate_blendshape() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="L">L</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.load">load() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="M">M</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.mirror_mapping">mirror_mapping (pose_wrangler.v2.main.UERBFAPI property)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.mirror_pose">mirror_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.mirror_rbf_solver">mirror_rbf_solver() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.mute_pose">mute_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="O">O</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="extensions.html#pose_wrangler.v2.model.base_extension.PoseWranglerExtension.on_context_changed">on_context_changed() (pose_wrangler.v2.model.base_extension.PoseWranglerExtension method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="P">P</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="extensions.html#pose_wrangler.v2.model.base_extension.PoseWranglerExtension">PoseWranglerExtension (class in pose_wrangler.v2.model.base_extension)</a>
</li>
</ul></td>
</tr></table>
<h2 id="R">R</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.rbf_solvers">rbf_solvers (pose_wrangler.v2.main.UERBFAPI property)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.remove_driven">remove_driven() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.remove_drivers">remove_drivers() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.rename_pose">rename_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="S">S</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.serialize">serialize() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.serialize_to_file">serialize_to_file() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.set_mirror_mapping">set_mirror_mapping() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="U">U</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI">UERBFAPI (class in pose_wrangler.v2.main)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.update_pose">update_pose() (pose_wrangler.v2.main.UERBFAPI method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="V">V</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.VERSION">VERSION (pose_wrangler.v2.main.UERBFAPI attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="api.html#pose_wrangler.v2.main.UERBFAPI.view">view (pose_wrangler.v2.main.UERBFAPI property)</a>
<ul>
<li><a href="extensions.html#pose_wrangler.v2.model.base_extension.PoseWranglerExtension.view">(pose_wrangler.v2.model.base_extension.PoseWranglerExtension property)</a>
</li>
</ul></li>
</ul></td>
</tr></table>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2022, Epic Games.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View File

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Pose Wrangler Documentation &mdash; Pose Wrangler 2.0.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="2.0.0 Overview" href="overview.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="#" class="icon icon-home"> Pose Wrangler
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="overview.html">2.0.0 Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="upgrading.html">Upgrading to 2.0.0</a></li>
<li class="toctree-l1"><a class="reference internal" href="mirror_mapping.html">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="#">Pose Wrangler</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="#" class="icon icon-home"></a> &raquo;</li>
<li>Pose Wrangler Documentation</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/index.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="pose-wrangler-documentation">
<h1>Pose Wrangler Documentation<a class="headerlink" href="#pose-wrangler-documentation" title="Permalink to this headline"></a></h1>
<div class="toctree-wrapper compound">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="overview.html">2.0.0 Overview</a><ul>
<li class="toctree-l2"><a class="reference internal" href="overview.html#ui-overview">UI Overview</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="upgrading.html">Upgrading to 2.0.0</a></li>
<li class="toctree-l1"><a class="reference internal" href="mirror_mapping.html">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a><ul>
<li class="toctree-l2"><a class="reference internal" href="api.html#pose-wrangler-v2-main">pose_wrangler.v2.main</a></li>
</ul>
</li>
</ul>
</div>
</section>
<section id="overview">
<h1>Overview<a class="headerlink" href="#overview" title="Permalink to this headline"></a></h1>
<p>PoseWrangler is a tool for interfacing with Epics MayaUERBFPlugin. The plugin is distributed by Epic Games and installed via Quixel Bridge.</p>
<section id="versions">
<h2>Versions<a class="headerlink" href="#versions" title="Permalink to this headline"></a></h2>
<section id="id1">
<h3>1.0.0:<a class="headerlink" href="#id1" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Supports legacy scenes created with the UE4RBFSolverNode</p></li>
<li><p>Supports Maya 2018-2022</p></li>
<li><p>Provides upgrade workflow to migrate to V2</p></li>
</ul>
</section>
<section id="id2">
<h3>2.0.0:<a class="headerlink" href="#id2" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Supports scenes created with the UERBFSolverNode</p></li>
<li><p>Multiple Driver Support</p></li>
<li><p>Initial blendshape support (WIP)</p></li>
<li><p>Supports Maya 2018-2022</p></li>
<li><p>Support for custom mirror mappings to allow for rigs with naming conventions that deviate from the default UE5 conventions</p></li>
<li><p>Fully automatable via Python and MayaPy</p></li>
<li><p>Serialization/deserialization to dictionary or JSON file</p></li>
<li><p>Support for custom extensions and context menu actions</p></li>
</ul>
</section>
</section>
<section id="contributors">
<h2>Contributors<a class="headerlink" href="#contributors" title="Permalink to this headline"></a></h2>
<ul class="simple">
<li><p>Chris Evans</p></li>
<li><p>Judd Simantov</p></li>
<li><p>David Corral</p></li>
<li><p>Borna Berc</p></li>
<li><p>Chris Theodosius</p></li>
</ul>
</section>
</section>
<section id="indices-and-tables">
<h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Permalink to this headline"></a></h1>
<ul class="simple">
<li><p><a class="reference internal" href="genindex.html"><span class="std std-ref">Index</span></a></p></li>
<li><p><a class="reference internal" href="py-modindex.html"><span class="std std-ref">Module Index</span></a></p></li>
<li><p><a class="reference internal" href="search.html"><span class="std std-ref">Search Page</span></a></p></li>
</ul>
</section>
</div>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="overview.html" class="btn btn-neutral float-right" title="2.0.0 Overview" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2022, Epic Games.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View File

@ -0,0 +1,168 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mirror Mapping &mdash; Pose Wrangler 2.0.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Extensions" href="extensions.html" />
<link rel="prev" title="Upgrading to 2.0.0" href="upgrading.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Pose Wrangler
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="overview.html">2.0.0 Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="upgrading.html">Upgrading to 2.0.0</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Pose Wrangler</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Mirror Mapping</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/mirror_mapping.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="mirror-mapping">
<h1>Mirror Mapping<a class="headerlink" href="#mirror-mapping" title="Permalink to this headline"></a></h1>
<p>Mirror mapping enables Pose Wrangler to support custom skeletons when mirroring RBF solvers and poses.</p>
<p>Below is the mirror mapping for characters following the MetaHuman naming conventions:</p>
<blockquote>
<div><div class="highlight-JSON notranslate"><div class="highlight"><pre><span></span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;solver_expression&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;(?P&lt;prefix&gt;[a-zA-Z0-9]+)?(?P&lt;side&gt;_[lr]{1}_)(?P&lt;suffix&gt;[a-zA-Z0-9_]+)&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;transform_expression&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;(?P&lt;prefix&gt;[a-zA-Z0-9_]+)?(?P&lt;side&gt;_[lr]{1}_)(?P&lt;suffix&gt;[a-zA-Z0-9_]+)&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;left&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;solver_syntax&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;_l_&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;transform_syntax&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;_l_&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">},</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;right&quot;</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;solver_syntax&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;_r_&quot;</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">&quot;transform_syntax&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;_r_&quot;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
</div>
</div></blockquote>
<table class="colwidths-given docutils align-default" id="id1">
<caption><span class="caption-text">Title</span><a class="headerlink" href="#id1" title="Permalink to this table"></a></caption>
<colgroup>
<col style="width: 33%" />
<col style="width: 67%" />
</colgroup>
<thead>
<tr class="row-odd"><th class="head"><p>Key</p></th>
<th class="head"><p>Definition</p></th>
</tr>
</thead>
<tbody>
<tr class="row-even"><td><p>solver_expression</p></td>
<td><p>A regular expression used to match against the solver node name</p></td>
</tr>
<tr class="row-odd"><td><p>transform_expression</p></td>
<td><p>A regular expression used to match against the transform (joint) node name</p></td>
</tr>
<tr class="row-even"><td><p>left</p></td>
<td><p>dictionary containing the expected match for the solver_expression and <br> transform_expression</p></td>
</tr>
<tr class="row-odd"><td><p>right</p></td>
<td><p>dictionary containing the expected match for the solver_expression and <br> transform_expression</p></td>
</tr>
</tbody>
</table>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>As an example if we wanted to mirror a solver called <code class="docutils literal notranslate"><span class="pre">calf_l_UERBFSolver</span></code> we first try to match the name
of the solver against the <code class="docutils literal notranslate"><span class="pre">solver_expression</span></code>. If the match fails, youll see an error appear in the output
log that the solver name doesnt match the settings defined in mirror mapping.</p>
<p>If the match succeeds, it will then iterate through the groups in the matchdict to determine if one of the groups
matches the <code class="docutils literal notranslate"><span class="pre">solver_syntax</span></code> for the left hand side. If it does, it will replace it with the right and vice versa.</p>
<p>In this case, we will end up with a matchdict that looks something like this: <br>
<code class="docutils literal notranslate"><span class="pre">{&quot;prefix&quot;:</span> <span class="pre">&quot;calf&quot;,</span> <span class="pre">&quot;side&quot;:</span> <span class="pre">&quot;_l_&quot;,</span> <span class="pre">&quot;suffix&quot;:</span> <span class="pre">&quot;UERBFSolver&quot;}</span></code> <br>
The mapping will iterate through this dictionary and attempt to match each value against the <code class="docutils literal notranslate"><span class="pre">solver_syntax</span></code> of
the <code class="docutils literal notranslate"><span class="pre">left</span></code> and <code class="docutils literal notranslate"><span class="pre">right</span></code> group defined in mirror mapping. If it matches the <code class="docutils literal notranslate"><span class="pre">left</span></code> group it will replace the
<code class="docutils literal notranslate"><span class="pre">side</span></code> value with the <code class="docutils literal notranslate"><span class="pre">solver_syntax</span></code> specified in the <code class="docutils literal notranslate"><span class="pre">right</span></code> group resulting in the following dictionary: <br>
<code class="docutils literal notranslate"><span class="pre">{&quot;prefix&quot;:</span> <span class="pre">&quot;calf&quot;,</span> <span class="pre">&quot;side&quot;:</span> <span class="pre">&quot;_r_&quot;,</span> <span class="pre">&quot;suffix&quot;:</span> <span class="pre">&quot;UERBFSolver&quot;}</span></code> <br></p>
<p>The same thing then happens for the drivers and driven transforms associated with the solver, using <code class="docutils literal notranslate"><span class="pre">transform_expression</span></code>
and <code class="docutils literal notranslate"><span class="pre">transform_syntax</span></code> in place of the solver.</p>
</div>
</section>
</div>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="upgrading.html" class="btn btn-neutral float-left" title="Upgrading to 2.0.0" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
<a href="extensions.html" class="btn btn-neutral float-right" title="Extensions" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2022, Epic Games.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
# Sphinx inventory version 2
# Project: Pose Wrangler
# Version:
# The remainder of this file is compressed using zlib.
xÚµMoÛ0 †ïþÖkŒ®ÇÞ6,rh´èvd±è ’œÆûõ“"Åv²`C,ïfPäCê5EI+ øÃÙp0åþ¡„Éò}ýúõû—íéþ±æÄZôÍÊÖ þé­
ý×°òÇúõmóò‰s†U<E280A0>ƒ„R\q<>Ô¶DC p­¢30Ô°=H켓Ý)#l>ÍÌgÔˆƒ%öHÁ+—aª¶Šû<C5A0>Í'uÆ€tŒ6Jƒqý<71>
(‘²¢`Á0ÂÙ/X<58>wF ¼c|> (sKè|ädªÒ2%mFë4àp­¤ó°Ù…ÆP ®zìú i<6D>€“Däá*Šîˆël®cù)ìTÞ1c~W <0B><08>,˜1Ê`A´f²ÉhÅÊ%1˜=¢Ë<C2A2>ƒc9Ô€P{H7òüZ&”ŒØ_Wþ,æÉ’?ÒÇ<C392>îÏPÖ8·þ4ÿÙ¿³P<C2B3>¦ÙO<>ÛZEù˶¬ˆ7¸Üz¯ŸÉi}²Nߥãűºô5^T½t8@ÄSa—Îᯮ4ÕqÝúE ÿ5Ý•}-EÐÖ:úHU<48>Vc; ß.Åä0q¹  !¥-<1A>LR8Ý9©€‡€“5ºoÂg1º%êÄ#ì<07>6„¾©ºþKœOQ\¦ à|%žŽ6ôm…WïJyº_<C2BA>R”¢<E2809D>?ó±P?ÕÌQÊIª“-º?”÷å=zI¶bûGžmï½<Og<4F>˜º=Œ¶ó×·¤<C2B7>¢Ó<C2A2>!ôB†Áýߧb¥Åoéa à

View File

@ -0,0 +1,177 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2.0.0 Overview &mdash; Pose Wrangler 2.0.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Upgrading to 2.0.0" href="upgrading.html" />
<link rel="prev" title="Pose Wrangler Documentation" href="index.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Pose Wrangler
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1 current"><a class="current reference internal" href="#">2.0.0 Overview</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#ui-overview">UI Overview</a><ul>
<li class="toctree-l3"><a class="reference internal" href="#solver-view">1) Solver View</a></li>
<li class="toctree-l3"><a class="reference internal" href="#pose-view">2) Pose View</a></li>
<li class="toctree-l3"><a class="reference internal" href="#driver-view">3) Driver View</a></li>
<li class="toctree-l3"><a class="reference internal" href="#driven-transforms-blendshape-view">4) Driven Transforms / Blendshape View</a></li>
<li class="toctree-l3"><a class="reference internal" href="#extensions-and-settings-view">5) Extensions and Settings View</a></li>
<li class="toctree-l3"><a class="reference internal" href="#top-menu">6) Top Menu</a></li>
<li class="toctree-l3"><a class="reference internal" href="#output-log">7) Output Log</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="upgrading.html">Upgrading to 2.0.0</a></li>
<li class="toctree-l1"><a class="reference internal" href="mirror_mapping.html">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Pose Wrangler</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>2.0.0 Overview</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/overview.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="overview">
<h1>2.0.0 Overview<a class="headerlink" href="#overview" title="Permalink to this headline"></a></h1>
<p>Version 2.0.0 is a refactor of the original Pose Wrangler. The goal of this version has been to separate the UI
logic from the backend, enabling users to automate RBF setup via Python and create custom extensions to add new
functionality.</p>
<section id="ui-overview">
<h2>UI Overview<a class="headerlink" href="#ui-overview" title="Permalink to this headline"></a></h2>
<a class="reference internal image-reference" href="_images/overview.png"><img alt="UI Overview" src="_images/overview.png" style="width: 800px;" /></a>
<section id="solver-view">
<h3>1) Solver View<a class="headerlink" href="#solver-view" title="Permalink to this headline"></a></h3>
<p>Here is where you can find a list of all the RBF solvers currently available in the scene and can edit, create, delete
or mirror solvers.</p>
<p>In order to make changes to the poses, drivers or driven transforms/blendshapes you need to toggle a solver into <cite>edit</cite>
mode via the <cite>Edit Selected Solver</cite> button.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>Only one solver can be edited at a single time.</p>
</div>
</section>
<section id="pose-view">
<h3>2) Pose View<a class="headerlink" href="#pose-view" title="Permalink to this headline"></a></h3>
<p>Here you can find a list of all the poses. In order to make any changes to the poses, you need to edit the corresponding solver.</p>
<div class="admonition warning">
<p class="admonition-title">Warning</p>
<p>Most of these are self-explanatory, but is it worth noting that <cite>Mute Pose</cite> will enable and disable the pose on the
solver via the <cite>targetEnable</cite> attribute. This means that if the RBF solver node is set to use <cite>automatic radius</cite>
then the radius will be calculated without the influence of the muted poses.</p>
</div>
</section>
<section id="driver-view">
<h3>3) Driver View<a class="headerlink" href="#driver-view" title="Permalink to this headline"></a></h3>
<p>2.0.0 supports multiple drivers. Here you can add/remove driver transforms.</p>
</section>
<section id="driven-transforms-blendshape-view">
<h3>4) Driven Transforms / Blendshape View<a class="headerlink" href="#driven-transforms-blendshape-view" title="Permalink to this headline"></a></h3>
<p>Here you can add/remove driven transforms and create/add blendshapes.</p>
<div class="admonition note">
<p class="admonition-title">Note</p>
<p>The blendshape workflow is currently a work in progress and may change in the future.</p>
</div>
</section>
<section id="extensions-and-settings-view">
<h3>5) Extensions and Settings View<a class="headerlink" href="#extensions-and-settings-view" title="Permalink to this headline"></a></h3>
<p>Pose Wrangler now supports writing custom extensions for the tool that can be embedded into the UI here.
<cite>Core Extensions</cite> are default extensions that ship with Pose Wrangler. <cite>Pose Exporter</cite> is an example of a custom
extension used by the MetaHuman team. For more information on how these extensions work and how to create your own,
please refer to the <a class="reference internal" href="extensions.html"><span class="doc">Extensions</span></a> page.</p>
<p>You can also change the mirror mapping, which will allow you to specify a custom mapping if your skeleton does not
match the default MetaHuman skeleton and the mirror solver/pose functionality is causing unexpected results.</p>
</section>
<section id="top-menu">
<h3>6) Top Menu<a class="headerlink" href="#top-menu" title="Permalink to this headline"></a></h3>
<p>Here you can import/export RBF solvers to/from a JSON file, change the theme and view the help.</p>
</section>
<section id="output-log">
<h3>7) Output Log<a class="headerlink" href="#output-log" title="Permalink to this headline"></a></h3>
<p>Displays debug, info, warning and error messages.</p>
</section>
</section>
</section>
</div>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="index.html" class="btn btn-neutral float-left" title="Pose Wrangler Documentation" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
<a href="upgrading.html" class="btn btn-neutral float-right" title="Upgrading to 2.0.0" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2022, Epic Games.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View File

@ -0,0 +1,119 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &mdash; Pose Wrangler 2.0.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<script src="_static/searchtools.js"></script>
<script src="_static/language_data.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="#" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Pose Wrangler
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="#" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="overview.html">2.0.0 Overview</a></li>
<li class="toctree-l1"><a class="reference internal" href="upgrading.html">Upgrading to 2.0.0</a></li>
<li class="toctree-l1"><a class="reference internal" href="mirror_mapping.html">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Pose Wrangler</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Search</li>
<li class="wy-breadcrumbs-aside">
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<noscript>
<div id="fallback" class="admonition warning">
<p class="last">
Please activate JavaScript to enable the search functionality.
</p>
</div>
</noscript>
<div id="search-results">
</div>
</div>
</div>
<footer>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2022, Epic Games.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
<script>
jQuery(function() { Search.loadIndex("searchindex.js"); });
</script>
<script id="searchindexloader"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,117 @@
<!DOCTYPE html>
<html class="writer-html5" lang="en" >
<head>
<meta charset="utf-8" /><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Upgrading to 2.0.0 &mdash; Pose Wrangler 2.0.0 documentation</title>
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/theme.css" type="text/css" />
<!--[if lt IE 9]>
<script src="_static/js/html5shiv.min.js"></script>
<![endif]-->
<script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
<script src="_static/jquery.js"></script>
<script src="_static/underscore.js"></script>
<script src="_static/doctools.js"></script>
<script src="_static/js/theme.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Mirror Mapping" href="mirror_mapping.html" />
<link rel="prev" title="2.0.0 Overview" href="overview.html" />
</head>
<body class="wy-body-for-nav">
<div class="wy-grid-for-nav">
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
<div class="wy-side-scroll">
<div class="wy-side-nav-search" >
<a href="index.html" class="icon icon-home"> Pose Wrangler
</a>
<div role="search">
<form id="rtd-search-form" class="wy-form" action="search.html" method="get">
<input type="text" name="q" placeholder="Search docs" />
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
</div><div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="Navigation menu">
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1"><a class="reference internal" href="overview.html">2.0.0 Overview</a></li>
<li class="toctree-l1 current"><a class="current reference internal" href="#">Upgrading to 2.0.0</a></li>
<li class="toctree-l1"><a class="reference internal" href="mirror_mapping.html">Mirror Mapping</a></li>
<li class="toctree-l1"><a class="reference internal" href="extensions.html">Extensions</a></li>
<li class="toctree-l1"><a class="reference internal" href="api.html">API</a></li>
</ul>
</div>
</div>
</nav>
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap"><nav class="wy-nav-top" aria-label="Mobile navigation menu" >
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
<a href="index.html">Pose Wrangler</a>
</nav>
<div class="wy-nav-content">
<div class="rst-content">
<div role="navigation" aria-label="Page navigation">
<ul class="wy-breadcrumbs">
<li><a href="index.html" class="icon icon-home"></a> &raquo;</li>
<li>Upgrading to 2.0.0</li>
<li class="wy-breadcrumbs-aside">
<a href="_sources/upgrading.rst.txt" rel="nofollow"> View page source</a>
</li>
</ul>
<hr/>
</div>
<div role="main" class="document" itemscope="itemscope" itemtype="http://schema.org/Article">
<div itemprop="articleBody">
<section id="upgrading-to-2-0-0">
<h1>Upgrading to 2.0.0<a class="headerlink" href="#upgrading-to-2-0-0" title="Permalink to this headline"></a></h1>
<p>When you load up Pose Wrangler with a scene that contains <cite>UE4RBFSolverNode</cite> you will be greeted with the new legacy
version of PoseWrangler. This version is almost identical with the version previously available via GitHub, with the
addition of the log window and the upgrade button.</p>
<p>If you have the new version of the plugin available you will be presented with an option to upgrade your scene at the
top of the Pose Wrangler window. Pressing this button will execute an upgrade script that will replace all the
<cite>UE4RBFSolverNode</cite> with <cite>UERBFSolverNode</cite> and clean up old/redundant nodes left lingering in the scene.</p>
<a class="reference internal image-reference" href="_images/upgrade.png"><img alt="UI Overview" src="_images/upgrade.png" style="width: 800px;" /></a>
<p>If the upgrade is successful you will be presented with the new tool window.</p>
<a class="reference internal image-reference" href="_images/v2.png"><img alt="UI Overview" src="_images/v2.png" style="width: 800px;" /></a>
</section>
</div>
</div>
<footer><div class="rst-footer-buttons" role="navigation" aria-label="Footer">
<a href="overview.html" class="btn btn-neutral float-left" title="2.0.0 Overview" accesskey="p" rel="prev"><span class="fa fa-arrow-circle-left" aria-hidden="true"></span> Previous</a>
<a href="mirror_mapping.html" class="btn btn-neutral float-right" title="Mirror Mapping" accesskey="n" rel="next">Next <span class="fa fa-arrow-circle-right" aria-hidden="true"></span></a>
</div>
<hr/>
<div role="contentinfo">
<p>&#169; Copyright 2022, Epic Games.</p>
</div>
Built with <a href="https://www.sphinx-doc.org/">Sphinx</a> using a
<a href="https://github.com/readthedocs/sphinx_rtd_theme">theme</a>
provided by <a href="https://readthedocs.org">Read the Docs</a>.
</footer>
</div>
</div>
</section>
</div>
<script>
jQuery(function () {
SphinxRtdTheme.Navigation.enable(true);
});
</script>
</body>
</html>

View File

@ -0,0 +1,7 @@
# Copyright Epic Games, Inc. All Rights Reserved.
import logging
# Grab the pose wrangler log
LOG = logging.getLogger("EpicGames.PoseWrangler")
# Set the logging level
LOG.setLevel(logging.DEBUG)

View File

@ -0,0 +1,31 @@
# Copyright Epic Games, Inc. All Rights Reserved.
import os
from epic_pose_wrangler.log import LOG
from epic_pose_wrangler.model.plugin_manager import PluginManager
class PoseWrangler(object):
"""
Main entrypoint for interacting with PoseWrangler. Will handle loading the correct version of the tool based on the
available version of the plugin
"""
def __init__(self, view=True):
# Get the current version of the tool
self._api = PluginManager.get_pose_wrangler(view=view, parent=self)
@property
def api(self):
return self._api
def upgrade(self, file_path=None, delete_file=False):
"""
Restart pose_wrangler with the specified serialized solver data file
:param file_path :type str: path to serialized json data
:param delete_file :type bool: should the file be deleted once the upgrade has completed?
"""
LOG.info("Rebooting PoseWrangler")
self._api = PluginManager.get_pose_wrangler(view=self._api.view, parent=self, file_path=file_path)
if delete_file:
os.remove(file_path)

View File

@ -0,0 +1,13 @@
# Copyright Epic Games, Inc. All Rights Reserved.
class RBFAPI(object):
"""
Base class for creating RBF API classes
"""
UPGRADE_AVAILABLE = False
VERSION = "0.0.0"
def __init__(self, view=False, parent=None, file_path=None):
super(RBFAPI, self).__init__()
self._view = view
self._parent = parent
self._file_path = file_path

View File

@ -0,0 +1,43 @@
# Copyright Epic Games, Inc. All Rights Reserved.
from epic_pose_wrangler.log import LOG
class PoseWranglerException(Exception):
"""
Base Exception for PoseWrangler related errors
"""
def __init__(self, message):
super(PoseWranglerException, self).__init__(message)
# Log the message as an error
LOG.error(message)
class InvalidPoseWranglerPlugin(PoseWranglerException, RuntimeError):
"""
Exception raised when no valid plugins could be loaded
"""
class PoseWranglerSettingsError(PoseWranglerException):
"""
Raised when a setting is invalid
"""
class InvalidMirrorMapping(PoseWranglerSettingsError):
"""
Raised when the mirror mapping is incorrect
"""
class PoseWranglerIOError(PoseWranglerException):
"""
Raised when issues with serialization/deserialization arise
"""
class PoseWranglerFunctionalityNotImplemented(PoseWranglerException):
"""
Raised when pose wrangler functionality hasn't been implemented yet
"""

View File

@ -0,0 +1,126 @@
# Copyright Epic Games, Inc. All Rights Reserved.
"""
Example Mapping (MetaHuman)
{
# Regular expression to validate that the solver follows the correct naming convention for mirroring
"solver_expression": "(?P<prefix>[a-zA-Z0-9]+)?(?P<side>_[lr]{1}_)(?P<suffix>[a-zA-Z0-9]+)",
# Regular expression to validate that the joint follows the correct naming convention for mirroring
"transform_expression": "(?P<prefix>[a-zA-Z0-9]+)?(?P<side>_[lr]{1}_)(?P<suffix>[a-zA-Z0-9]+)",
"left": {
# Left side syntax for the solver
"solver_syntax": "_l_",
# Left side syntax for the joint
"transform_syntax": "_l_"
},
"right": {
# Right side syntax for the solver
"solver_syntax": "_r_",
# Right side syntax for the joint
"transform_syntax": "_r_"
}
}
"""
import json
import os
from epic_pose_wrangler.log import LOG
class MirrorMapping(object):
"""
Class for managing mirror settings
"""
LEFT = "left"
RIGHT = "right"
def __init__(self, file_path=None, source_side="left"):
# Make a list of valid mappings that should exist in the mirror mapping file
self._valid_mappings = [MirrorMapping.LEFT, MirrorMapping.RIGHT]
# If no file path is specified, use the MetaHuman config as the fallback
if file_path is None:
LOG.debug("No mirror mapping specified, using default MetaHuman conventions")
file_path = os.path.join(
os.path.dirname(os.path.dirname(__file__)),
'resources',
'mirror_mappings',
'metahuman.json'
)
self._file_path = file_path
# Load the json mapping data
with open(file_path, 'r') as f:
self._mapping_data = json.loads(f.read())
# Set the solver expression from the file
self._solver_expression = self._mapping_data['solver_expression']
# Set the transform expression from the file
self._transform_expression = self._mapping_data['transform_expression']
# Set the source side and create defaults
self._source_side = source_side
self._source_mapping_data = {}
self._source_solver_syntax = ""
self._source_transform_syntax = ""
self._target_mapping_data = {}
self._target_solver_syntax = ""
self._target_transform_syntax = ""
# Set the source side property to trigger the default values to be updated
self.source_side = source_side
@property
def file_path(self):
return self._file_path
@property
def solver_expression(self):
return self._solver_expression
@property
def transform_expression(self):
return self._transform_expression
@property
def source_side(self):
return self._source_side
@source_side.setter
def source_side(self, side):
"""
Sets the source side and updates the source/target values accordingly
:param side: MirrorMapping.LEFT or MirrorMapping.RIGHT
"""
if side not in self._valid_mappings:
raise ValueError("Invalid side specified, options are: {}".format(", ".join(self._valid_mappings)))
self._source_side = side
self._source_mapping_data = self._mapping_data[self._source_side]
self._source_solver_syntax = self._source_mapping_data['solver_syntax']
self._source_transform_syntax = self._source_mapping_data['transform_syntax']
self._target_mapping_data = self._mapping_data[
MirrorMapping.RIGHT if self._source_side == MirrorMapping.LEFT else MirrorMapping.LEFT]
self._target_solver_syntax = self._target_mapping_data['solver_syntax']
self._target_transform_syntax = self._target_mapping_data['transform_syntax']
@property
def source_solver_syntax(self):
return self._source_solver_syntax
@property
def source_transform_syntax(self):
return self._source_transform_syntax
@property
def target_solver_syntax(self):
return self._target_solver_syntax
@property
def target_transform_syntax(self):
return self._target_transform_syntax
def swap_sides(self):
"""
Swap the source side to the opposite of the current side.
"""
new_target = MirrorMapping.LEFT if self.source_side == MirrorMapping.RIGHT else MirrorMapping.RIGHT
self.source_side = new_target

View File

@ -0,0 +1,111 @@
# Copyright Epic Games, Inc. All Rights Reserved.
from collections import OrderedDict
from maya import cmds
from epic_pose_wrangler.log import LOG
from epic_pose_wrangler.model import exceptions
class PluginManager:
"""
Class for loading latest available plugin and managing pose_wrangler versions
"""
# The name of the recommended solver
RECOMMENDED_SOLVER = "UERBFSolverNode"
# Empty list to keep track of the loaded solver nodes
LOADED_NODES = []
# Generate an ordered manifest of known plugin name variants with the newest plugins
__PLUGIN_VERSIONS = OrderedDict(
{
"MayaUERBFPlugin_{}".format(cmds.about(version=True)): "UERBFSolverNode",
"MayaUERBFPlugin{}".format(cmds.about(version=True)): "UERBFSolverNode",
"MayaUERBFPlugin": "UERBFSolverNode",
"MayaUE4RBFPlugin_{}".format(cmds.about(version=True)): "UE4RBFSolverNode",
"MayaUE4RBFPlugin{}".format(cmds.about(version=True)): "UE4RBFSolverNode",
"MayaUE4RBFPlugin": "UE4RBFSolverNode"}
)
@staticmethod
def load_plugin():
"""
Load any valid RBF plugins
:return :type list: node names loaded
"""
PluginManager.LOADED_NODES = []
# Iterate through all of the valid plugin versions and attempt to load
for plugin_name, solver_name in PluginManager.__PLUGIN_VERSIONS.items():
# If the plugin is already loaded, add the solver name to the list of loaded nodes
if cmds.pluginInfo(plugin_name, q=True, loaded=True) and solver_name not in PluginManager.LOADED_NODES:
PluginManager.LOADED_NODES.append(solver_name)
else:
try:
# Attempt to load the plugin
cmds.loadPlugin(plugin_name)
# If the solver name is not already in the list, add it
if solver_name not in PluginManager.LOADED_NODES:
PluginManager.LOADED_NODES.append(solver_name)
# Ignore errors
except RuntimeError as e:
pass
# If we have no loaded nodes no plugin loaded correctly
if not PluginManager.LOADED_NODES:
raise exceptions.InvalidPoseWranglerPlugin("Unable to load valid RBF plugin version.")
return PluginManager.LOADED_NODES
@staticmethod
def is_scene_using_recommended_solver():
"""
Scan the current scene to find which version of the solver is being used
:return :type bool: is the recommended solver being used for all RBF nodes
"""
solvers = []
# Get a list of the solver names
for solver_node_name in list(PluginManager.__PLUGIN_VERSIONS.values()):
if solver_node_name not in solvers:
solvers.append(solver_node_name)
# Iterate through the solver names
for solver_node_name in solvers:
# Check if any solvers exist in the scene of the specified type and check if the solver name is the
# recommended name. If we have old solvers in the scene, we aren't using the latest version.
if cmds.ls(type=solver_node_name) and solver_node_name != PluginManager.RECOMMENDED_SOLVER:
return False
return True
@staticmethod
def get_pose_wrangler(view=True, parent=None, file_path=None):
"""
Get the correct version of the pose wrangler tool depending on the available plugins and nodes in the scene
:param view :type bool: Should we be displaying a UI to the user?
:param parent :type main.PoseWrangler: reference to the main entry point for the tool, used for
restarting/upgrading the tool
:param file_path :type str: (optional) path to a json file containing serialized solver data
:return :type object: reference to the currently loaded version of pose wrangler
"""
# Load the RBF plugin
loaded_nodes = PluginManager.load_plugin()
# If the recommended solver is not loaded, fall back to the original pose wrangler implementation
if PluginManager.RECOMMENDED_SOLVER not in loaded_nodes:
LOG.warning("You are currently using an outdated plugin. Certain functionality may be limited.")
from epic_pose_wrangler.v1 import main
return main.UE4RBFAPI(view=view, parent=parent)
# Bool to keep track of importing the newest api version
import_failed = False
# Check if the scene uses the latest solver
if PluginManager.is_scene_using_recommended_solver():
# Try and import the latest tool version
try:
from epic_pose_wrangler.v2 import main
return main.UERBFAPI(view=view, parent=parent, file_path=file_path)
except ImportError as e:
LOG.error("Unable to import API v2, falling back to API v1 - {exception}".format(exception=e))
import_failed = True
# Fall back to API v1
from epic_pose_wrangler.v1 import main
# If the recommended solver is available but finds old nodes in the scene and imports correctly, provide
# the option to upgrade to the latest version
if not import_failed:
main.UE4RBFAPI.UPGRADE_AVAILABLE = True
return main.UE4RBFAPI(view=view, parent=parent)

View File

@ -0,0 +1,48 @@
import os
from PySide2 import QtCore
from epic_pose_wrangler.log import LOG
from epic_pose_wrangler.model import exceptions
class SettingsManager(object):
"""
Settings Manager for reading/writing to PoseWrangler settings ini file
"""
QSETTINGS = None
def __init__(self):
# Initialize the QSettings
QtCore.QSettings.setPath(QtCore.QSettings.IniFormat, QtCore.QSettings.UserScope, os.environ['LOCALAPPDATA'])
# Store the QSettings
self.__class__.QSETTINGS = QtCore.QSettings(
QtCore.QSettings.IniFormat,
QtCore.QSettings.UserScope,
"Epic Games",
"PoseWrangler"
)
self.__class__.QSETTINGS.setFallbacksEnabled(False)
LOG.debug("Successfully initialized SettingsManager")
@classmethod
def get_setting(cls, name):
"""
Get the setting with the specified name
:param name :type str: setting name
:return :type str or None: setting value
"""
# If the settings haven't been initialized, raise exception
if cls.QSETTINGS is None:
raise exceptions.PoseWranglerSettingsError("Unable to load settings, "
"{cls} must be initialized first".format(cls=cls))
return cls.QSETTINGS.value(name, None)
@classmethod
def set_setting(cls, name, value):
"""
Add/Overwrite the setting with the specified name and value
:param name :type str: setting name
:param value :type any: setting value
"""
cls.QSETTINGS.setValue(name, value)

View File

@ -0,0 +1,201 @@
import sys
import inspect
import maya
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.cmds as cmds
from pymel.core.windows import Callback, CallbackWithArgs
StreamTypesPerSubjectType = {
"Prop": ["Root Only", "Full Hierarchy"],
"Character": ["Root Only", "Full Hierarchy"],
"Camera": ["Root Only", "Full Hierarchy", "Camera"],
"Light": ["Root Only", "Full Hierarchy", "Light"],
}
def OnRemoveSubject(SubjectPath):
cmds.LiveLinkRemoveSubject(SubjectPath)
RefreshSubjects()
def CreateSubjectTable():
cmds.rowColumnLayout("SubjectLayout", numberOfColumns=5, columnWidth=[(1, 20), (2,80), (3, 100), (4, 180), (5, 120)], columnOffset=[(1, 'right', 5), (2, 'right', 10), (4, 'left', 10)], parent="SubjectWrapperLayout")
cmds.text(label="")
cmds.text(label="Subject Type", font="boldLabelFont", align="left")
cmds.text(label="Subject Name", font="boldLabelFont", align="left")
cmds.text(label="DAG Path", font="boldLabelFont", align="left")
cmds.text(label="Stream Type", font="boldLabelFont", align="left")
cmds.rowColumnLayout("SubjectLayout", edit=True, rowOffset=(1, "bottom", 10))
#Populate subjects list from c++
def PopulateSubjects():
SubjectNames = cmds.LiveLinkSubjectNames()
SubjectPaths = cmds.LiveLinkSubjectPaths()
SubjectTypes = cmds.LiveLinkSubjectTypes()
SubjectRoles = cmds.LiveLinkSubjectRoles()
if SubjectPaths is not None:
RowCounter = 0
for (SubjectName, SubjectPath, SubjectType, SubjectRole) in zip(SubjectNames, SubjectPaths, SubjectTypes, SubjectRoles):
cmds.button(label="-", height=21, command=Callback(OnRemoveSubject, SubjectPath), parent="SubjectLayout")
cmds.text(label=SubjectType, height=21, align="left", parent="SubjectLayout")
cmds.textField(text=SubjectName, height=21, changeCommand=CallbackWithArgs(cmds.LiveLinkChangeSubjectName, SubjectPath), parent="SubjectLayout")
cmds.text(label=SubjectPath, align="left", height=21, parent="SubjectLayout")
LayoutName = "ColumnLayoutRow_" + str(RowCounter) # adding a trailing index makes the name unique which is required by the api
cmds.columnLayout(LayoutName, parent="SubjectLayout")
cmds.optionMenu("SubjectTypeMenu", parent=LayoutName, height=21, changeCommand=CallbackWithArgs(cmds.LiveLinkChangeSubjectStreamType, SubjectPath))
for StreamType in StreamTypesPerSubjectType[SubjectType]:
cmds.menuItem(label=StreamType)
StreamTypeIndex = StreamTypesPerSubjectType[SubjectType].index(SubjectRole) + 1 # menu items are 1-indexed
cmds.optionMenu("SubjectTypeMenu", edit=True, select=StreamTypeIndex)
RowCounter += 1
def ClearSubjects():
if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)):
cmds.deleteUI("SubjectLayout")
#Refresh subjects list
def RefreshSubjects():
if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)):
cmds.deleteUI("SubjectLayout")
CreateSubjectTable()
PopulateSubjects()
#Connection UI Colours
ConnectionActiveColour = [0.71, 0.9, 0.1]
ConnectionInactiveColour = [1.0, 0.4, 0.4]
ConnectionColourMap = {
True : ConnectionActiveColour,
False: ConnectionInactiveColour
}
#Base class for command (common creator method + allows for automatic register/unregister)
class LiveLinkCommand(OpenMayaMPx.MPxCommand):
def __init__(self):
OpenMayaMPx.MPxCommand.__init__(self)
@classmethod
def Creator(Cls):
return OpenMayaMPx.asMPxPtr( Cls() )
# Is supplied object a live link command
def IsLiveLinkCommand(InCls):
return inspect.isclass(InCls) and issubclass(InCls, LiveLinkCommand) and InCls != LiveLinkCommand
# Given a list of strings of names return all the live link commands listed
def GetLiveLinkCommandsFromModule(ModuleItems):
EvalItems = (eval(Item) for Item in ModuleItems)
return [Command for Command in EvalItems if IsLiveLinkCommand(Command) ]
# Command to create the Live Link UI
class MayaLiveLinkUI(LiveLinkCommand):
WindowName = "MayaLiveLinkUI"
Title = "Maya Live Link UI"
WindowSize = (500, 300)
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
if (cmds.window(self.WindowName , exists=True)):
cmds.deleteUI(self.WindowName)
window = cmds.window( self.WindowName, title=self.Title, widthHeight=(self.WindowSize[0], self.WindowSize[1]) )
#Get current connection status
ConnectionText, ConnectedState = cmds.LiveLinkConnectionStatus()
cmds.columnLayout( "mainColumn", adjustableColumn=True )
cmds.rowLayout("HeaderRow", numberOfColumns=3, adjustableColumn=1, parent = "mainColumn")
cmds.text(label="Unreal Engine Live Link", align="left")
cmds.text(label="Connection:")
cmds.text("ConnectionStatusUI", label=ConnectionText, align="center", backgroundColor=ConnectionColourMap[ConnectedState], width=150)
cmds.separator(h=20, style="none", parent="mainColumn")
cmds.columnLayout("SubjectWrapperLayout", parent="mainColumn") # just used as a container that will survive refreshing, so the following controls stay in their correct place
CreateSubjectTable()
PopulateSubjects()
cmds.separator(h=20, style="none", parent="mainColumn")
cmds.button( label='Add Selection', parent = "mainColumn", command=self.AddSelection)
cmds.showWindow( self.WindowName )
def AddSelection(self, *args):
cmds.LiveLinkAddSelection()
RefreshSubjects()
# Command to Refresh the subject UI
class MayaLiveLinkRefreshUI(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
RefreshSubjects()
class MayaLiveLinkClearUI(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
def doIt(self, argList):
ClearSubjects()
CreateSubjectTable()
# Command to Refresh the connection UI
class MayaLiveLinkRefreshConnectionUI(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)):
#Get current connection status
ConnectionText, ConnectedState = cmds.LiveLinkConnectionStatus()
cmds.text("ConnectionStatusUI", edit=True, label=ConnectionText, backgroundColor=ConnectionColourMap[ConnectedState])
class MayaLiveLinkGetActiveCamera(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
self.clearResult()
try:
c = cmds.getPanel(wf=1)
cam = cmds.modelPanel(c, q=True, camera=True)
except:
pass
else:
self.setResult(cam)
#Grab commands declared
Commands = GetLiveLinkCommandsFromModule(dir())
#Initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
print("LiveLink:"
for Command in Commands:
try:
print("\tRegistering Command '%s'"%Command.__name__
mplugin.registerCommand( Command.__name__, Command.Creator )
except:
sys.stderr.write( "Failed to register command: %s\n" % Command.__name__ )
raise
# Uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
for Command in Commands:
try:
mplugin.deregisterCommand( Command.__name__ )
except:
sys.stderr.write( "Failed to unregister command: %s\n" % Command.__name__ )

View File

@ -0,0 +1,201 @@
import sys
import inspect
import maya
import maya.OpenMaya as OpenMaya
import maya.OpenMayaMPx as OpenMayaMPx
import maya.cmds as cmds
from pymel.core.windows import Callback, CallbackWithArgs
StreamTypesPerSubjectType = {
"Prop": ["Root Only", "Full Hierarchy"],
"Character": ["Root Only", "Full Hierarchy"],
"Camera": ["Root Only", "Full Hierarchy", "Camera"],
"Light": ["Root Only", "Full Hierarchy", "Light"],
}
def OnRemoveSubject(SubjectPath):
cmds.LiveLinkRemoveSubject(SubjectPath)
RefreshSubjects()
def CreateSubjectTable():
cmds.rowColumnLayout("SubjectLayout", numberOfColumns=5, columnWidth=[(1, 20), (2,80), (3, 100), (4, 180), (5, 120)], columnOffset=[(1, 'right', 5), (2, 'right', 10), (4, 'left', 10)], parent="SubjectWrapperLayout")
cmds.text(label="")
cmds.text(label="Subject Type", font="boldLabelFont", align="left")
cmds.text(label="Subject Name", font="boldLabelFont", align="left")
cmds.text(label="DAG Path", font="boldLabelFont", align="left")
cmds.text(label="Stream Type", font="boldLabelFont", align="left")
cmds.rowColumnLayout("SubjectLayout", edit=True, rowOffset=(1, "bottom", 10))
#Populate subjects list from c++
def PopulateSubjects():
SubjectNames = cmds.LiveLinkSubjectNames()
SubjectPaths = cmds.LiveLinkSubjectPaths()
SubjectTypes = cmds.LiveLinkSubjectTypes()
SubjectRoles = cmds.LiveLinkSubjectRoles()
if SubjectPaths is not None:
RowCounter = 0
for (SubjectName, SubjectPath, SubjectType, SubjectRole) in zip(SubjectNames, SubjectPaths, SubjectTypes, SubjectRoles):
cmds.button(label="-", height=21, command=Callback(OnRemoveSubject, SubjectPath), parent="SubjectLayout")
cmds.text(label=SubjectType, height=21, align="left", parent="SubjectLayout")
cmds.textField(text=SubjectName, height=21, changeCommand=CallbackWithArgs(cmds.LiveLinkChangeSubjectName, SubjectPath), parent="SubjectLayout")
cmds.text(label=SubjectPath, align="left", height=21, parent="SubjectLayout")
LayoutName = "ColumnLayoutRow_" + str(RowCounter) # adding a trailing index makes the name unique which is required by the api
cmds.columnLayout(LayoutName, parent="SubjectLayout")
cmds.optionMenu("SubjectTypeMenu", parent=LayoutName, height=21, changeCommand=CallbackWithArgs(cmds.LiveLinkChangeSubjectStreamType, SubjectPath))
for StreamType in StreamTypesPerSubjectType[SubjectType]:
cmds.menuItem(label=StreamType)
StreamTypeIndex = StreamTypesPerSubjectType[SubjectType].index(SubjectRole) + 1 # menu items are 1-indexed
cmds.optionMenu("SubjectTypeMenu", edit=True, select=StreamTypeIndex)
RowCounter += 1
def ClearSubjects():
if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)):
cmds.deleteUI("SubjectLayout")
#Refresh subjects list
def RefreshSubjects():
if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)):
cmds.deleteUI("SubjectLayout")
CreateSubjectTable()
PopulateSubjects()
#Connection UI Colours
ConnectionActiveColour = [0.71, 0.9, 0.1]
ConnectionInactiveColour = [1.0, 0.4, 0.4]
ConnectionColourMap = {
True : ConnectionActiveColour,
False: ConnectionInactiveColour
}
#Base class for command (common creator method + allows for automatic register/unregister)
class LiveLinkCommand(OpenMayaMPx.MPxCommand):
def __init__(self):
OpenMayaMPx.MPxCommand.__init__(self)
@classmethod
def Creator(Cls):
return OpenMayaMPx.asMPxPtr( Cls() )
# Is supplied object a live link command
def IsLiveLinkCommand(InCls):
return inspect.isclass(InCls) and issubclass(InCls, LiveLinkCommand) and InCls != LiveLinkCommand
# Given a list of strings of names return all the live link commands listed
def GetLiveLinkCommandsFromModule(ModuleItems):
EvalItems = (eval(Item) for Item in ModuleItems)
return [Command for Command in EvalItems if IsLiveLinkCommand(Command) ]
# Command to create the Live Link UI
class MayaLiveLinkUI(LiveLinkCommand):
WindowName = "MayaLiveLinkUI"
Title = "Maya Live Link UI"
WindowSize = (500, 300)
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
if (cmds.window(self.WindowName , exists=True)):
cmds.deleteUI(self.WindowName)
window = cmds.window( self.WindowName, title=self.Title, widthHeight=(self.WindowSize[0], self.WindowSize[1]) )
#Get current connection status
ConnectionText, ConnectedState = cmds.LiveLinkConnectionStatus()
cmds.columnLayout( "mainColumn", adjustableColumn=True )
cmds.rowLayout("HeaderRow", numberOfColumns=3, adjustableColumn=1, parent = "mainColumn")
cmds.text(label="Unreal Engine Live Link", align="left")
cmds.text(label="Connection:")
cmds.text("ConnectionStatusUI", label=ConnectionText, align="center", backgroundColor=ConnectionColourMap[ConnectedState], width=150)
cmds.separator(h=20, style="none", parent="mainColumn")
cmds.columnLayout("SubjectWrapperLayout", parent="mainColumn") # just used as a container that will survive refreshing, so the following controls stay in their correct place
CreateSubjectTable()
PopulateSubjects()
cmds.separator(h=20, style="none", parent="mainColumn")
cmds.button( label='Add Selection', parent = "mainColumn", command=self.AddSelection)
cmds.showWindow( self.WindowName )
def AddSelection(self, *args):
cmds.LiveLinkAddSelection()
RefreshSubjects()
# Command to Refresh the subject UI
class MayaLiveLinkRefreshUI(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
RefreshSubjects()
class MayaLiveLinkClearUI(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
def doIt(self, argList):
ClearSubjects()
CreateSubjectTable()
# Command to Refresh the connection UI
class MayaLiveLinkRefreshConnectionUI(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
if (cmds.window(MayaLiveLinkUI.WindowName , exists=True)):
#Get current connection status
ConnectionText, ConnectedState = cmds.LiveLinkConnectionStatus()
cmds.text("ConnectionStatusUI", edit=True, label=ConnectionText, backgroundColor=ConnectionColourMap[ConnectedState])
class MayaLiveLinkGetActiveCamera(LiveLinkCommand):
def __init__(self):
LiveLinkCommand.__init__(self)
# Invoked when the command is run.
def doIt(self,argList):
self.clearResult()
try:
c = cmds.getPanel(wf=1)
cam = cmds.modelPanel(c, q=True, camera=True)
except:
pass
else:
self.setResult(cam)
#Grab commands declared
Commands = GetLiveLinkCommandsFromModule(dir())
#Initialize the script plug-in
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
print("LiveLink:"
for Command in Commands:
try:
print("\tRegistering Command '%s'"%Command.__name__
mplugin.registerCommand( Command.__name__, Command.Creator )
except:
sys.stderr.write( "Failed to register command: %s\n" % Command.__name__ )
raise
# Uninitialize the script plug-in
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
for Command in Commands:
try:
mplugin.deregisterCommand( Command.__name__ )
except:
sys.stderr.write( "Failed to unregister command: %s\n" % Command.__name__ )