mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into tests/publish_process
This commit is contained in:
commit
2fe59d26d4
84 changed files with 1795 additions and 2351 deletions
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,8 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.17.0
|
||||
- 3.16.7
|
||||
- 3.16.7-nightly.2
|
||||
- 3.16.7-nightly.1
|
||||
- 3.16.6
|
||||
|
|
@ -133,8 +135,6 @@ body:
|
|||
- 3.14.10-nightly.3
|
||||
- 3.14.10-nightly.2
|
||||
- 3.14.10-nightly.1
|
||||
- 3.14.9
|
||||
- 3.14.9-nightly.5
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
373
CHANGELOG.md
373
CHANGELOG.md
|
|
@ -1,6 +1,379 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## [3.17.0](https://github.com/ynput/OpenPype/tree/3.17.0)
|
||||
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.16.7...3.17.0)
|
||||
|
||||
### **🚀 Enhancements**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Remove schema from OpenPype root <a href="https://github.com/ynput/OpenPype/pull/5355">#5355</a></summary>
|
||||
|
||||
Remove unused schema directory in root of repository which was moved inside openpype/pipeline/schema.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Igniter: Allow custom Qt scale factor rounding policy <a href="https://github.com/ynput/OpenPype/pull/5554">#5554</a></summary>
|
||||
|
||||
Do not force `PassThrough` rounding policy if different policy is defined via env variable.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🐛 Bug fixes**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Lower urllib3 to support older OpenSSL <a href="https://github.com/ynput/OpenPype/pull/5538">#5538</a></summary>
|
||||
|
||||
Lowered `urllib3` to `1.26.16` to support older OpenSSL.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Do not try to add schema to zip files <a href="https://github.com/ynput/OpenPype/pull/5557">#5557</a></summary>
|
||||
|
||||
Do not add `schema` folder to zip file. This fixes issue cause by https://github.com/ynput/OpenPype/pull/5355 .
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: Lower click dependency version <a href="https://github.com/ynput/OpenPype/pull/5629">#5629</a></summary>
|
||||
|
||||
Lower click version to support older versions of python.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **Merged pull requests**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bump certifi from 2023.5.7 to 2023.7.22 <a href="https://github.com/ynput/OpenPype/pull/5351">#5351</a></summary>
|
||||
|
||||
Bumps [certifi](https://github.com/certifi/python-certifi) from 2023.5.7 to 2023.7.22.
|
||||
<details>
|
||||
<summary>Commits</summary>
|
||||
<ul>
|
||||
<li><a href="https://github.com/certifi/python-certifi/commit/8fb96ed81f71e7097ed11bc4d9b19afd7ea5c909"><code>8fb96ed</code></a> 2023.07.22</li>
|
||||
<li><a href="https://github.com/certifi/python-certifi/commit/afe77220e0eaa722593fc5d294213ff5275d1b40"><code>afe7722</code></a> Bump actions/setup-python from 4.6.1 to 4.7.0 (<a href="https://redirect.github.com/certifi/python-certifi/issues/230">#230</a>)</li>
|
||||
<li><a href="https://github.com/certifi/python-certifi/commit/2038739ad56abec7aaddfa90ad2ce6b3ed7f5c7b"><code>2038739</code></a> Bump dessant/lock-threads from 3.0.0 to 4.0.1 (<a href="https://redirect.github.com/certifi/python-certifi/issues/229">#229</a>)</li>
|
||||
<li><a href="https://github.com/certifi/python-certifi/commit/44df761f4c09d19f32b3cc09208a739043a5e25b"><code>44df761</code></a> Hash pin Actions and enable dependabot (<a href="https://redirect.github.com/certifi/python-certifi/issues/228">#228</a>)</li>
|
||||
<li>See full diff in <a href="https://github.com/certifi/python-certifi/compare/2023.05.07...2023.07.22">compare view</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<br />
|
||||
|
||||
|
||||
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
|
||||
|
||||
You can trigger a rebase of this PR by commenting `@dependabot rebase`.
|
||||
|
||||
[//]: # (dependabot-automerge-start)
|
||||
[//]: # (dependabot-automerge-end)
|
||||
|
||||
---
|
||||
|
||||
<details>
|
||||
<summary>Dependabot commands and options</summary>
|
||||
<br />
|
||||
|
||||
You can trigger Dependabot actions by commenting on this PR:
|
||||
- `@dependabot rebase` will rebase this PR
|
||||
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
|
||||
- `@dependabot merge` will merge this PR after your CI passes on it
|
||||
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
|
||||
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
|
||||
- `@dependabot reopen` will reopen this PR if it is closed
|
||||
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
|
||||
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
|
||||
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
|
||||
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
|
||||
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ynput/OpenPype/network/alerts).
|
||||
|
||||
</details>
|
||||
|
||||
> **Note**
|
||||
> Automatic rebases have been disabled on this pull request as it has been open for over 30 days.
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.16.7](https://github.com/ynput/OpenPype/tree/3.16.7)
|
||||
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.16.6...3.16.7)
|
||||
|
||||
### **🆕 New features**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Extract active view as thumbnail when no thumbnail set <a href="https://github.com/ynput/OpenPype/pull/5426">#5426</a></summary>
|
||||
|
||||
This sets the Maya instance's thumbnail to the current active view if no thumbnail was set yet.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Implement USD publish and load using native `mayaUsdPlugin` <a href="https://github.com/ynput/OpenPype/pull/5573">#5573</a></summary>
|
||||
|
||||
Implement Creator and Loaders for extraction and loading of USD files using Maya's own `mayaUsdPlugin`.Also adds support to load a `usd` file into an Arnold Standin (`aiStandin`) and assigning looks to it.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Ignore separated modules <a href="https://github.com/ynput/OpenPype/pull/5619">#5619</a></summary>
|
||||
|
||||
Do not load already separated modules from default directory.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🚀 Enhancements**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Reduce amount of code for Collect Looks <a href="https://github.com/ynput/OpenPype/pull/5253">#5253</a></summary>
|
||||
|
||||
- Refactor `get_file_node_files` because popping from `paths` by index should have been done in reversed order anyway. It's now changed to not need popping at all.
|
||||
- Removed unused `RENDERER_NODE_TYPES` and if-branch which collected `node_attrs` list which was unused + collected members which was also done outside of the if branch and thus generated no extra data.
|
||||
- Collected all materials from look set attributes at once instead of multiple queries
|
||||
- Collected all file nodes in history from a single query instead of per type
|
||||
- Restructured assignment of `instance.data["resources"]` to be more readable
|
||||
- Cached `PXR_NODES` only ones (Note: plugin load is checked on discovery of the collect look plugin) instead of querying plugin load and its nodes per file node per attribute
|
||||
- Removed some debug logs or combined some messages
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Mark deprecated settings in Maya <a href="https://github.com/ynput/OpenPype/pull/5627">#5627</a></summary>
|
||||
|
||||
Added deprecated info to docstrings of maya colormanagement settings.Resolves: https://github.com/ynput/OpenPype/issues/5556
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Max: switching versions of maxScene maintain parentage/links with the loaders <a href="https://github.com/ynput/OpenPype/pull/5424">#5424</a></summary>
|
||||
|
||||
When using scene inventory to manage or update the version of the loading objects, the linked modifiers or parentage of the objects would be kept.Meanwhile, loaded objects from all loaders no longer parented to the container with OP Data.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>3ds max: small tweaks to obj extractor and model publishing flow <a href="https://github.com/ynput/OpenPype/pull/5605">#5605</a></summary>
|
||||
|
||||
There migh be situation where OBJ Extractor passes without failure, but no obj file is produced. This is adding simple check directly into the extractor to catch it earlier then in the integration phase. Also switched `Validate USD Plugin` to optional, because it was always run no matter if the Extract USD was enabled or not, hindering testing (and publishing).
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>TVPaint: Plugin can be reopened <a href="https://github.com/ynput/OpenPype/pull/5610">#5610</a></summary>
|
||||
|
||||
TVPaint plugin can be reopened.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Remove context prompt <a href="https://github.com/ynput/OpenPype/pull/5632">#5632</a></summary>
|
||||
|
||||
More of a plea than a PR, but could we please remove the context prompt in Maya when switching tasks?
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>General: Create a desktop icon is checked <a href="https://github.com/ynput/OpenPype/pull/5636">#5636</a></summary>
|
||||
|
||||
In OP Installer `Create a desktop icon` is checked by default.
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🐛 Bug fixes**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Extract look is not AYON compatible - OP-5375 <a href="https://github.com/ynput/OpenPype/pull/5341">#5341</a></summary>
|
||||
|
||||
The textures that would use hardlinking are going through texture processors. Currently all texture processors are hardcoded to copy texture instead of respecting the settings of forcing to copy.The texture processors were last modified 4 months ago, so effectively all clients that are on any pipeline updated in the last 4 months wont be utilizing hardlinking at all, since the hardcoded texture processors will copy texture no matter the OS.This opts for completely disabling the hardlinking feature, while we figure out what to do about it.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: Multiverse USD Override inherit from correct new style creator <a href="https://github.com/ynput/OpenPype/pull/5566">#5566</a></summary>
|
||||
|
||||
Fix Creator for Multiverse USD Override by inheriting from correct new style creator class type
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Max: Bug Fix Alembic Loaders with Ornatrix <a href="https://github.com/ynput/OpenPype/pull/5434">#5434</a></summary>
|
||||
|
||||
Bugfix the alembic loader with both ornatrix alembic and max alembic supportsAdd the ornatrix alembic loaders for loading the alembic with Ornatrix-related modifiers.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Avoid creation of duplicated links <a href="https://github.com/ynput/OpenPype/pull/5593">#5593</a></summary>
|
||||
|
||||
Handle cases when an existing link should be recreated and do not create the same link multitple times during single publishing.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Extract Review: Multilayer specification for ffmpeg <a href="https://github.com/ynput/OpenPype/pull/5613">#5613</a></summary>
|
||||
|
||||
Extract review is specifying layer name when exr is multilayer.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Fussion: added support for Fusion 17 <a href="https://github.com/ynput/OpenPype/pull/5614">#5614</a></summary>
|
||||
|
||||
Fusion 17 still uses Python 3.6 which causes issues with some our delivered libraries. Vendorized necessary set for Python 3.6
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Publisher: Fix screenshot widget <a href="https://github.com/ynput/OpenPype/pull/5615">#5615</a></summary>
|
||||
|
||||
Use correct super method name.EDITED:Removed fade animation which is not triggered at some cases, e.g. in Nuke the animation does not start. I do expect that is caused by `exec_` on the dialog, which blocks event processing to the animation, even when I've added the window as parent it still didn't trigger registered callback.Modified how the "empty" space is not filled by using paths instead of clear mode on painter. Added render hints to add antialiasing.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Photoshop: auto_images without alpha will not fail <a href="https://github.com/ynput/OpenPype/pull/5620">#5620</a></summary>
|
||||
|
||||
ExtractReview caused issue on `auto_image` instance without alpha channel, this fixes it.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Fix - _id key used instead of id in get_last_version_by_subset_name <a href="https://github.com/ynput/OpenPype/pull/5626">#5626</a></summary>
|
||||
|
||||
Just 'id' is not returned because value in fields. Caused KeyError.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Bugfix: create symlinks for ssl libs on Centos 7 <a href="https://github.com/ynput/OpenPype/pull/5633">#5633</a></summary>
|
||||
|
||||
Docker build was missing `libssl.1.1.so` and `libcrypto.1.1.so` symlinks needed by the executable itself, because Python is now explicitly built with OpenSSL 1.1.1
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **📃 Documentation**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Documentation/local settings <a href="https://github.com/ynput/OpenPype/pull/5102">#5102</a></summary>
|
||||
|
||||
I completed the "Working with local settings" page. I updated the screenshot, wrote an explanation for each empty category, and if available, linked the more detailed pages already existing. I also added the "Environments" category.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.16.6](https://github.com/ynput/OpenPype/tree/3.16.6)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -109,6 +109,8 @@ RUN source $HOME/.bashrc \
|
|||
RUN cp /usr/lib64/libffi* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/openssl11/libssl* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/openssl11/libcrypto* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& ln -sr ./build/exe.linux-x86_64-3.9/lib/libssl.so ./build/exe.linux-x86_64-3.9/lib/libssl.1.1.so \
|
||||
&& ln -sr ./build/exe.linux-x86_64-3.9/lib/libcrypto.so ./build/exe.linux-x86_64-3.9/lib/libcrypto.1.1.so \
|
||||
&& cp /root/.pyenv/versions/${OPENPYPE_PYTHON_VERSION}/lib/libpython* ./build/exe.linux-x86_64-3.9/lib \
|
||||
&& cp /usr/lib64/libxcb* ./build/exe.linux-x86_64-3.9/vendor/python/PySide2/Qt/lib
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,11 @@ def _get_qt_app():
|
|||
if attr is not None:
|
||||
QtWidgets.QApplication.setAttribute(attr)
|
||||
|
||||
if hasattr(QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy"):
|
||||
policy = os.getenv("QT_SCALE_FACTOR_ROUNDING_POLICY")
|
||||
if (
|
||||
hasattr(QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy")
|
||||
and not policy
|
||||
):
|
||||
QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy(
|
||||
QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough
|
||||
)
|
||||
|
|
|
|||
|
|
@ -589,7 +589,7 @@ class BootstrapRepos:
|
|||
self.registry = OpenPypeSettingsRegistry()
|
||||
self.zip_filter = [".pyc", "__pycache__"]
|
||||
self.openpype_filter = [
|
||||
"openpype", "schema", "LICENSE"
|
||||
"openpype", "LICENSE"
|
||||
]
|
||||
|
||||
# dummy progress reporter
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ WizardStyle=modern
|
|||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
|
||||
|
||||
[InstallDelete]
|
||||
; clean everything in previous installation folder
|
||||
|
|
@ -53,4 +53,3 @@ Name: "{autodesktop}\{#MyAppName} {#AppVer}"; Filename: "{app}\openpype_gui.exe"
|
|||
|
||||
[Run]
|
||||
Filename: "{app}\openpype_gui.exe"; Description: "{cm:LaunchProgram,OpenPype}"; Flags: nowait postinstall skipifsilent
|
||||
|
||||
|
|
|
|||
|
|
@ -700,3 +700,50 @@ def get_output_children(output_node, include_sops=True):
|
|||
out_list += [out]
|
||||
|
||||
return out_list
|
||||
|
||||
|
||||
def get_resolution_from_doc(doc):
|
||||
"""Get resolution from the given asset document. """
|
||||
|
||||
if not doc or "data" not in doc:
|
||||
print("Entered document is not valid. \"{}\"".format(str(doc)))
|
||||
return None
|
||||
|
||||
resolution_width = doc["data"].get("resolutionWidth")
|
||||
resolution_height = doc["data"].get("resolutionHeight")
|
||||
|
||||
# Make sure both width and height are set
|
||||
if resolution_width is None or resolution_height is None:
|
||||
print("No resolution information found for \"{}\"".format(doc["name"]))
|
||||
return None
|
||||
|
||||
return int(resolution_width), int(resolution_height)
|
||||
|
||||
|
||||
def set_camera_resolution(camera, asset_doc=None):
|
||||
"""Apply resolution to camera from asset document of the publish"""
|
||||
|
||||
if not asset_doc:
|
||||
asset_doc = get_current_project_asset()
|
||||
|
||||
resolution = get_resolution_from_doc(asset_doc)
|
||||
|
||||
if resolution:
|
||||
print("Setting camera resolution: {} -> {}x{}".format(
|
||||
camera.name(), resolution[0], resolution[1]
|
||||
))
|
||||
camera.parm("resx").set(resolution[0])
|
||||
camera.parm("resy").set(resolution[1])
|
||||
|
||||
|
||||
def get_camera_from_container(container):
|
||||
"""Get camera from container node. """
|
||||
|
||||
cameras = container.recursiveGlob(
|
||||
"*",
|
||||
filter=hou.nodeTypeFilter.ObjCamera,
|
||||
include_subnets=False
|
||||
)
|
||||
|
||||
assert len(cameras) == 1, "Camera instance must have only one camera"
|
||||
return cameras[0]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import pyblish.api
|
|||
from openpype.pipeline import (
|
||||
register_creator_plugin_path,
|
||||
register_loader_plugin_path,
|
||||
register_inventory_action_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
)
|
||||
from openpype.pipeline.load import any_outdated_containers
|
||||
|
|
@ -55,6 +56,7 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
pyblish.api.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
register_inventory_action_path(INVENTORY_PATH)
|
||||
|
||||
log.info("Installing callbacks ... ")
|
||||
# register_event_callback("init", on_init)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
from openpype.pipeline import InventoryAction
|
||||
from openpype.hosts.houdini.api.lib import (
|
||||
get_camera_from_container,
|
||||
set_camera_resolution
|
||||
)
|
||||
from openpype.pipeline.context_tools import get_current_project_asset
|
||||
|
||||
|
||||
class SetCameraResolution(InventoryAction):
|
||||
|
||||
label = "Set Camera Resolution"
|
||||
icon = "desktop"
|
||||
color = "orange"
|
||||
|
||||
@staticmethod
|
||||
def is_compatible(container):
|
||||
return (
|
||||
container.get("loader") == "CameraLoader"
|
||||
)
|
||||
|
||||
def process(self, containers):
|
||||
asset_doc = get_current_project_asset()
|
||||
for container in containers:
|
||||
node = container["node"]
|
||||
camera = get_camera_from_container(node)
|
||||
set_camera_resolution(camera, asset_doc)
|
||||
|
|
@ -4,6 +4,13 @@ from openpype.pipeline import (
|
|||
)
|
||||
from openpype.hosts.houdini.api import pipeline
|
||||
|
||||
from openpype.hosts.houdini.api.lib import (
|
||||
set_camera_resolution,
|
||||
get_camera_from_container
|
||||
)
|
||||
|
||||
import hou
|
||||
|
||||
|
||||
ARCHIVE_EXPRESSION = ('__import__("_alembic_hom_extensions")'
|
||||
'.alembicGetCameraDict')
|
||||
|
|
@ -25,7 +32,15 @@ def transfer_non_default_values(src, dest, ignore=None):
|
|||
channel expression and ignore certain Parm types.
|
||||
|
||||
"""
|
||||
import hou
|
||||
|
||||
ignore_types = {
|
||||
hou.parmTemplateType.Toggle,
|
||||
hou.parmTemplateType.Menu,
|
||||
hou.parmTemplateType.Button,
|
||||
hou.parmTemplateType.FolderSet,
|
||||
hou.parmTemplateType.Separator,
|
||||
hou.parmTemplateType.Label,
|
||||
}
|
||||
|
||||
src.updateParmStates()
|
||||
|
||||
|
|
@ -62,14 +77,6 @@ def transfer_non_default_values(src, dest, ignore=None):
|
|||
continue
|
||||
|
||||
# Ignore folders, separators, etc.
|
||||
ignore_types = {
|
||||
hou.parmTemplateType.Toggle,
|
||||
hou.parmTemplateType.Menu,
|
||||
hou.parmTemplateType.Button,
|
||||
hou.parmTemplateType.FolderSet,
|
||||
hou.parmTemplateType.Separator,
|
||||
hou.parmTemplateType.Label,
|
||||
}
|
||||
if parm.parmTemplate().type() in ignore_types:
|
||||
continue
|
||||
|
||||
|
|
@ -90,13 +97,8 @@ class CameraLoader(load.LoaderPlugin):
|
|||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
import os
|
||||
import hou
|
||||
|
||||
# Format file name, Houdini only wants forward slashes
|
||||
file_path = self.filepath_from_context(context)
|
||||
file_path = os.path.normpath(file_path)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
file_path = self.filepath_from_context(context).replace("\\", "/")
|
||||
|
||||
# Get the root node
|
||||
obj = hou.node("/obj")
|
||||
|
|
@ -106,19 +108,21 @@ class CameraLoader(load.LoaderPlugin):
|
|||
node_name = "{}_{}".format(namespace, name) if namespace else name
|
||||
|
||||
# Create a archive node
|
||||
container = self.create_and_connect(obj, "alembicarchive", node_name)
|
||||
node = self.create_and_connect(obj, "alembicarchive", node_name)
|
||||
|
||||
# TODO: add FPS of project / asset
|
||||
container.setParms({"fileName": file_path,
|
||||
"channelRef": True})
|
||||
node.setParms({"fileName": file_path, "channelRef": True})
|
||||
|
||||
# Apply some magic
|
||||
container.parm("buildHierarchy").pressButton()
|
||||
container.moveToGoodPosition()
|
||||
node.parm("buildHierarchy").pressButton()
|
||||
node.moveToGoodPosition()
|
||||
|
||||
# Create an alembic xform node
|
||||
nodes = [container]
|
||||
nodes = [node]
|
||||
|
||||
camera = get_camera_from_container(node)
|
||||
self._match_maya_render_mask(camera)
|
||||
set_camera_resolution(camera, asset_doc=context["asset"])
|
||||
self[:] = nodes
|
||||
|
||||
return pipeline.containerise(node_name,
|
||||
|
|
@ -143,14 +147,14 @@ class CameraLoader(load.LoaderPlugin):
|
|||
# Store the cam temporarily next to the Alembic Archive
|
||||
# so that we can preserve parm values the user set on it
|
||||
# after build hierarchy was triggered.
|
||||
old_camera = self._get_camera(node)
|
||||
old_camera = get_camera_from_container(node)
|
||||
temp_camera = old_camera.copyTo(node.parent())
|
||||
|
||||
# Rebuild
|
||||
node.parm("buildHierarchy").pressButton()
|
||||
|
||||
# Apply values to the new camera
|
||||
new_camera = self._get_camera(node)
|
||||
new_camera = get_camera_from_container(node)
|
||||
transfer_non_default_values(temp_camera,
|
||||
new_camera,
|
||||
# The hidden uniform scale attribute
|
||||
|
|
@ -158,6 +162,9 @@ class CameraLoader(load.LoaderPlugin):
|
|||
# "icon_scale" just skip that completely
|
||||
ignore={"scale"})
|
||||
|
||||
self._match_maya_render_mask(new_camera)
|
||||
set_camera_resolution(new_camera)
|
||||
|
||||
temp_camera.destroy()
|
||||
|
||||
def remove(self, container):
|
||||
|
|
@ -165,15 +172,6 @@ class CameraLoader(load.LoaderPlugin):
|
|||
node = container["node"]
|
||||
node.destroy()
|
||||
|
||||
def _get_camera(self, node):
|
||||
import hou
|
||||
cameras = node.recursiveGlob("*",
|
||||
filter=hou.nodeTypeFilter.ObjCamera,
|
||||
include_subnets=False)
|
||||
|
||||
assert len(cameras) == 1, "Camera instance must have only one camera"
|
||||
return cameras[0]
|
||||
|
||||
def create_and_connect(self, node, node_type, name=None):
|
||||
"""Create a node within a node which and connect it to the input
|
||||
|
||||
|
|
@ -194,5 +192,20 @@ class CameraLoader(load.LoaderPlugin):
|
|||
new_node.moveToGoodPosition()
|
||||
return new_node
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
def _match_maya_render_mask(self, camera):
|
||||
"""Workaround to match Maya render mask in Houdini"""
|
||||
|
||||
# print("Setting match maya render mask ")
|
||||
parm = camera.parm("aperture")
|
||||
expression = parm.expression()
|
||||
expression = expression.replace("return ", "aperture = ")
|
||||
expression += """
|
||||
# Match maya render mask (logic from Houdini's own FBX importer)
|
||||
node = hou.pwd()
|
||||
resx = node.evalParm('resx')
|
||||
resy = node.evalParm('resy')
|
||||
aspect = node.evalParm('aspect')
|
||||
aperture *= min(1, (resx / resy * aspect) / 1.5)
|
||||
return aperture
|
||||
"""
|
||||
parm.setExpression(expression, language=hou.exprLanguage.Python)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from openpype.hosts.max.api import lib
|
|||
from openpype.hosts.max.api.plugin import MS_CUSTOM_ATTRIB
|
||||
from openpype.hosts.max import MAX_HOST_DIR
|
||||
|
||||
|
||||
from pymxs import runtime as rt # noqa
|
||||
|
||||
log = logging.getLogger("openpype.hosts.max")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class ModelAbcLoader(load.LoaderPlugin):
|
|||
"""Loading model with the Alembic loader."""
|
||||
|
||||
families = ["model"]
|
||||
label = "Load Model(Alembic)"
|
||||
label = "Load Model with Alembic"
|
||||
representations = ["abc"]
|
||||
order = -10
|
||||
icon = "code-fork"
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class AbcLoader(load.LoaderPlugin):
|
|||
selections = rt.GetCurrentSelection()
|
||||
for abc in selections:
|
||||
for cam_shape in abc.Children:
|
||||
cam_shape.playbackType = 2
|
||||
cam_shape.playbackType = 0
|
||||
|
||||
namespace = unique_namespace(
|
||||
name + "_",
|
||||
|
|
|
|||
108
openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py
Normal file
108
openpype/hosts/max/plugins/load/load_pointcache_ornatrix.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import os
|
||||
from openpype.pipeline import load, get_representation_path
|
||||
from openpype.pipeline.load import LoadError
|
||||
from openpype.hosts.max.api.pipeline import (
|
||||
containerise,
|
||||
get_previous_loaded_object,
|
||||
update_custom_attribute_data
|
||||
)
|
||||
|
||||
from openpype.hosts.max.api.lib import (
|
||||
unique_namespace,
|
||||
get_namespace,
|
||||
object_transform_set,
|
||||
get_plugins
|
||||
)
|
||||
from openpype.hosts.max.api import lib
|
||||
from pymxs import runtime as rt
|
||||
|
||||
|
||||
class OxAbcLoader(load.LoaderPlugin):
|
||||
"""Ornatrix Alembic loader."""
|
||||
|
||||
families = ["camera", "animation", "pointcache"]
|
||||
label = "Load Alembic with Ornatrix"
|
||||
representations = ["abc"]
|
||||
order = -10
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
postfix = "param"
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
plugin_list = get_plugins()
|
||||
if "ephere.plugins.autodesk.max.ornatrix.dlo" not in plugin_list:
|
||||
raise LoadError("Ornatrix plugin not "
|
||||
"found/installed in Max yet..")
|
||||
|
||||
file_path = os.path.normpath(self.filepath_from_context(context))
|
||||
rt.AlembicImport.ImportToRoot = True
|
||||
rt.AlembicImport.CustomAttributes = True
|
||||
rt.importFile(
|
||||
file_path, rt.name("noPrompt"),
|
||||
using=rt.Ornatrix_Alembic_Importer)
|
||||
|
||||
scene_object = []
|
||||
for obj in rt.rootNode.Children:
|
||||
obj_type = rt.ClassOf(obj)
|
||||
if str(obj_type).startswith("Ox_"):
|
||||
scene_object.append(obj)
|
||||
|
||||
namespace = unique_namespace(
|
||||
name + "_",
|
||||
suffix="_",
|
||||
)
|
||||
abc_container = []
|
||||
for abc in scene_object:
|
||||
abc.name = f"{namespace}:{abc.name}"
|
||||
abc_container.append(abc)
|
||||
|
||||
return containerise(
|
||||
name, abc_container, context,
|
||||
namespace, loader=self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
path = get_representation_path(representation)
|
||||
node_name = container["instance_node"]
|
||||
namespace, name = get_namespace(node_name)
|
||||
node = rt.getNodeByName(node_name)
|
||||
node_list = get_previous_loaded_object(node)
|
||||
rt.Select(node_list)
|
||||
selections = rt.getCurrentSelection()
|
||||
transform_data = object_transform_set(selections)
|
||||
for prev_obj in selections:
|
||||
if rt.isValidNode(prev_obj):
|
||||
rt.Delete(prev_obj)
|
||||
|
||||
rt.AlembicImport.ImportToRoot = False
|
||||
rt.AlembicImport.CustomAttributes = True
|
||||
rt.importFile(
|
||||
path, rt.name("noPrompt"),
|
||||
using=rt.Ornatrix_Alembic_Importer)
|
||||
|
||||
scene_object = []
|
||||
for obj in rt.rootNode.Children:
|
||||
obj_type = rt.ClassOf(obj)
|
||||
if str(obj_type).startswith("Ox_"):
|
||||
scene_object.append(obj)
|
||||
ox_abc_objects = []
|
||||
for abc in scene_object:
|
||||
abc.Parent = container
|
||||
abc.name = f"{namespace}:{abc.name}"
|
||||
ox_abc_objects.append(abc)
|
||||
ox_transform = f"{abc.name}.transform"
|
||||
if ox_transform in transform_data.keys():
|
||||
abc.pos = transform_data[ox_transform] or 0
|
||||
abc.scale = transform_data[f"{abc.name}.scale"] or 0
|
||||
update_custom_attribute_data(node, ox_abc_objects)
|
||||
lib.imprint(
|
||||
container["instance_node"],
|
||||
{"representation": str(representation["_id"])},
|
||||
)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
node = rt.GetNodeByName(container["instance_node"])
|
||||
rt.Delete(node)
|
||||
|
|
@ -3,6 +3,7 @@ import pyblish.api
|
|||
from openpype.pipeline import publish, OptionalPyblishPluginMixin
|
||||
from pymxs import runtime as rt
|
||||
from openpype.hosts.max.api import maintained_selection
|
||||
from openpype.pipeline.publish import KnownPublishError
|
||||
|
||||
|
||||
class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin):
|
||||
|
|
@ -27,6 +28,7 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin):
|
|||
filepath = os.path.join(stagingdir, filename)
|
||||
self.log.info("Writing OBJ '%s' to '%s'" % (filepath, stagingdir))
|
||||
|
||||
self.log.info("Performing Extraction ...")
|
||||
with maintained_selection():
|
||||
# select and export
|
||||
node_list = instance.data["members"]
|
||||
|
|
@ -38,7 +40,10 @@ class ExtractModelObj(publish.Extractor, OptionalPyblishPluginMixin):
|
|||
using=rt.ObjExp,
|
||||
)
|
||||
|
||||
self.log.info("Performing Extraction ...")
|
||||
if not os.path.exists(filepath):
|
||||
raise KnownPublishError(
|
||||
"File {} wasn't produced by 3ds max, please check the logs.")
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Validator for USD plugin."""
|
||||
from openpype.pipeline import PublishValidationError
|
||||
from pyblish.api import InstancePlugin, ValidatorOrder
|
||||
from pymxs import runtime as rt
|
||||
|
||||
from openpype.pipeline import (
|
||||
OptionalPyblishPluginMixin,
|
||||
PublishValidationError
|
||||
)
|
||||
|
||||
|
||||
def get_plugins() -> list:
|
||||
"""Get plugin list from 3ds max."""
|
||||
|
|
@ -17,17 +21,25 @@ def get_plugins() -> list:
|
|||
return plugin_info_list
|
||||
|
||||
|
||||
class ValidateUSDPlugin(InstancePlugin):
|
||||
class ValidateUSDPlugin(OptionalPyblishPluginMixin,
|
||||
InstancePlugin):
|
||||
"""Validates if USD plugin is installed or loaded in 3ds max."""
|
||||
|
||||
order = ValidatorOrder - 0.01
|
||||
families = ["model"]
|
||||
hosts = ["max"]
|
||||
label = "USD Plugin"
|
||||
label = "Validate USD Plugin loaded"
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
"""Plugin entry point."""
|
||||
|
||||
for sc in ValidateUSDPlugin.__subclasses__():
|
||||
self.log.info(sc)
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
plugin_info = get_plugins()
|
||||
usd_import = "usdimport.dli"
|
||||
if usd_import not in plugin_info:
|
||||
|
|
|
|||
|
|
@ -659,17 +659,6 @@ def on_task_changed():
|
|||
lib.set_context_settings()
|
||||
lib.update_content_on_context_change()
|
||||
|
||||
msg = " project: {}\n asset: {}\n task:{}".format(
|
||||
get_current_project_name(),
|
||||
get_current_asset_name(),
|
||||
get_current_task_name()
|
||||
)
|
||||
|
||||
lib.show_message(
|
||||
"Context was changed",
|
||||
("Context was changed to:\n{}".format(msg)),
|
||||
)
|
||||
|
||||
|
||||
def before_workfile_open():
|
||||
if handle_workfile_locks():
|
||||
|
|
|
|||
|
|
@ -129,18 +129,49 @@ class MayaCreatorBase(object):
|
|||
shared_data["maya_cached_legacy_subsets"] = cache_legacy
|
||||
return shared_data
|
||||
|
||||
def get_publish_families(self):
|
||||
"""Return families for the instances of this creator.
|
||||
|
||||
Allow a Creator to define multiple families so that a creator can
|
||||
e.g. specify `usd` and `usdMaya` and another USD creator can also
|
||||
specify `usd` but apply different extractors like `usdMultiverse`.
|
||||
|
||||
There is no need to override this method if you only have the
|
||||
primary family defined by the `family` property as that will always
|
||||
be set.
|
||||
|
||||
Returns:
|
||||
list: families for instances of this creator
|
||||
|
||||
"""
|
||||
return []
|
||||
|
||||
def imprint_instance_node(self, node, data):
|
||||
|
||||
# We never store the instance_node as value on the node since
|
||||
# it's the node name itself
|
||||
data.pop("instance_node", None)
|
||||
|
||||
# Don't store `families` since it's up to the creator itself
|
||||
# to define the initial publish families - not a stored attribute of
|
||||
# `families`
|
||||
data.pop("families", None)
|
||||
|
||||
# We store creator attributes at the root level and assume they
|
||||
# will not clash in names with `subset`, `task`, etc. and other
|
||||
# default names. This is just so these attributes in many cases
|
||||
# are still editable in the maya UI by artists.
|
||||
# pop to move to end of dict to sort attributes last on the node
|
||||
# note: pop to move to end of dict to sort attributes last on the node
|
||||
creator_attributes = data.pop("creator_attributes", {})
|
||||
|
||||
# We only flatten value types which `imprint` function supports
|
||||
json_creator_attributes = {}
|
||||
for key, value in dict(creator_attributes).items():
|
||||
if isinstance(value, (list, tuple, dict)):
|
||||
creator_attributes.pop(key)
|
||||
json_creator_attributes[key] = value
|
||||
|
||||
# Flatten remaining creator attributes to the node itself
|
||||
data.update(creator_attributes)
|
||||
|
||||
# We know the "publish_attributes" will be complex data of
|
||||
|
|
@ -150,6 +181,10 @@ class MayaCreatorBase(object):
|
|||
data.pop("publish_attributes", {})
|
||||
)
|
||||
|
||||
# Persist the non-flattened creator attributes (special value types,
|
||||
# like multiselection EnumDef)
|
||||
data["creator_attributes"] = json.dumps(json_creator_attributes)
|
||||
|
||||
# Since we flattened the data structure for creator attributes we want
|
||||
# to correctly detect which flattened attributes should end back in the
|
||||
# creator attributes when reading the data from the node, so we store
|
||||
|
|
@ -170,15 +205,22 @@ class MayaCreatorBase(object):
|
|||
# being read as 'data'
|
||||
node_data.pop("cbId", None)
|
||||
|
||||
# Make sure we convert any creator attributes from the json string
|
||||
creator_attributes = node_data.get("creator_attributes")
|
||||
if creator_attributes:
|
||||
node_data["creator_attributes"] = json.loads(creator_attributes)
|
||||
else:
|
||||
node_data["creator_attributes"] = {}
|
||||
|
||||
# Move the relevant attributes into "creator_attributes" that
|
||||
# we flattened originally
|
||||
node_data["creator_attributes"] = {}
|
||||
creator_attribute_keys = node_data.pop("__creator_attributes_keys",
|
||||
"").split(",")
|
||||
for key in creator_attribute_keys:
|
||||
if key in node_data:
|
||||
node_data["creator_attributes"][key] = node_data.pop(key)
|
||||
|
||||
# Make sure we convert any publish attributes from the json string
|
||||
publish_attributes = node_data.get("publish_attributes")
|
||||
if publish_attributes:
|
||||
node_data["publish_attributes"] = json.loads(publish_attributes)
|
||||
|
|
@ -186,6 +228,11 @@ class MayaCreatorBase(object):
|
|||
# Explicitly re-parse the node name
|
||||
node_data["instance_node"] = node
|
||||
|
||||
# If the creator plug-in specifies
|
||||
families = self.get_publish_families()
|
||||
if families:
|
||||
node_data["families"] = families
|
||||
|
||||
return node_data
|
||||
|
||||
def _default_collect_instances(self):
|
||||
|
|
@ -230,6 +277,14 @@ class MayaCreator(NewCreator, MayaCreatorBase):
|
|||
if pre_create_data.get("use_selection"):
|
||||
members = cmds.ls(selection=True)
|
||||
|
||||
# Allow a Creator to define multiple families
|
||||
publish_families = self.get_publish_families()
|
||||
if publish_families:
|
||||
families = instance_data.setdefault("families", [])
|
||||
for family in self.get_publish_families():
|
||||
if family not in families:
|
||||
families.append(family)
|
||||
|
||||
with lib.undo_chunk():
|
||||
instance_node = cmds.sets(members, name=subset_name)
|
||||
instance_data["instance_node"] = instance_node
|
||||
|
|
|
|||
102
openpype/hosts/maya/plugins/create/create_maya_usd.py
Normal file
102
openpype/hosts/maya/plugins/create/create_maya_usd.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
from openpype.hosts.maya.api import plugin, lib
|
||||
from openpype.lib import (
|
||||
BoolDef,
|
||||
EnumDef,
|
||||
TextDef
|
||||
)
|
||||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
class CreateMayaUsd(plugin.MayaCreator):
|
||||
"""Create Maya USD Export"""
|
||||
|
||||
identifier = "io.openpype.creators.maya.mayausd"
|
||||
label = "Maya USD"
|
||||
family = "usd"
|
||||
icon = "cubes"
|
||||
description = "Create Maya USD Export"
|
||||
|
||||
cache = {}
|
||||
|
||||
def get_publish_families(self):
|
||||
return ["usd", "mayaUsd"]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
|
||||
if "jobContextItems" not in self.cache:
|
||||
# Query once instead of per instance
|
||||
job_context_items = {}
|
||||
try:
|
||||
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
|
||||
job_context_items = {
|
||||
cmds.mayaUSDListJobContexts(jobContext=name): name
|
||||
for name in cmds.mayaUSDListJobContexts(export=True) or []
|
||||
}
|
||||
except RuntimeError:
|
||||
# Likely `mayaUsdPlugin` plug-in not available
|
||||
self.log.warning("Unable to retrieve available job "
|
||||
"contexts for `mayaUsdPlugin` exports")
|
||||
|
||||
if not job_context_items:
|
||||
# enumdef multiselection may not be empty
|
||||
job_context_items = ["<placeholder; do not use>"]
|
||||
|
||||
self.cache["jobContextItems"] = job_context_items
|
||||
|
||||
defs = lib.collect_animation_defs()
|
||||
defs.extend([
|
||||
EnumDef("defaultUSDFormat",
|
||||
label="File format",
|
||||
items={
|
||||
"usdc": "Binary",
|
||||
"usda": "ASCII"
|
||||
},
|
||||
default="usdc"),
|
||||
BoolDef("stripNamespaces",
|
||||
label="Strip Namespaces",
|
||||
tooltip=(
|
||||
"Remove namespaces during export. By default, "
|
||||
"namespaces are exported to the USD file in the "
|
||||
"following format: nameSpaceExample_pPlatonic1"
|
||||
),
|
||||
default=True),
|
||||
BoolDef("mergeTransformAndShape",
|
||||
label="Merge Transform and Shape",
|
||||
tooltip=(
|
||||
"Combine Maya transform and shape into a single USD"
|
||||
"prim that has transform and geometry, for all"
|
||||
" \"geometric primitives\" (gprims).\n"
|
||||
"This results in smaller and faster scenes. Gprims "
|
||||
"will be \"unpacked\" back into transform and shape "
|
||||
"nodes when imported into Maya from USD."
|
||||
),
|
||||
default=True),
|
||||
BoolDef("includeUserDefinedAttributes",
|
||||
label="Include User Defined Attributes",
|
||||
tooltip=(
|
||||
"Whether to include all custom maya attributes found "
|
||||
"on nodes as metadata (userProperties) in USD."
|
||||
),
|
||||
default=False),
|
||||
TextDef("attr",
|
||||
label="Custom Attributes",
|
||||
default="",
|
||||
placeholder="attr1, attr2"),
|
||||
TextDef("attrPrefix",
|
||||
label="Custom Attributes Prefix",
|
||||
default="",
|
||||
placeholder="prefix1, prefix2"),
|
||||
EnumDef("jobContext",
|
||||
label="Job Context",
|
||||
items=self.cache["jobContextItems"],
|
||||
tooltip=(
|
||||
"Specifies an additional export context to handle.\n"
|
||||
"These usually contain extra schemas, primitives,\n"
|
||||
"and materials that are to be exported for a "
|
||||
"specific\ntask, a target renderer for example."
|
||||
),
|
||||
multiselection=True),
|
||||
])
|
||||
|
||||
return defs
|
||||
|
|
@ -14,6 +14,10 @@ class CreateMultiverseUsd(plugin.MayaCreator):
|
|||
label = "Multiverse USD Asset"
|
||||
family = "usd"
|
||||
icon = "cubes"
|
||||
description = "Create Multiverse USD Asset"
|
||||
|
||||
def get_publish_families(self):
|
||||
return ["usd", "mvUsd"]
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from openpype.lib import (
|
|||
)
|
||||
|
||||
|
||||
class CreateMultiverseUsdOver(plugin.Creator):
|
||||
class CreateMultiverseUsdOver(plugin.MayaCreator):
|
||||
"""Create Multiverse USD Override"""
|
||||
|
||||
identifier = "io.openpype.creators.maya.mvusdoverride"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from openpype.hosts.maya.api.lib import (
|
|||
)
|
||||
from openpype.hosts.maya.api.pipeline import containerise
|
||||
|
||||
|
||||
def is_sequence(files):
|
||||
sequence = False
|
||||
collections, remainder = clique.assemble(files, minimum_items=1)
|
||||
|
|
@ -29,11 +30,12 @@ def get_current_session_fps():
|
|||
session_fps = float(legacy_io.Session.get('AVALON_FPS', 25))
|
||||
return convert_to_maya_fps(session_fps)
|
||||
|
||||
|
||||
class ArnoldStandinLoader(load.LoaderPlugin):
|
||||
"""Load as Arnold standin"""
|
||||
|
||||
families = ["ass", "animation", "model", "proxyAbc", "pointcache"]
|
||||
representations = ["ass", "abc"]
|
||||
families = ["ass", "animation", "model", "proxyAbc", "pointcache", "usd"]
|
||||
representations = ["ass", "abc", "usda", "usdc", "usd"]
|
||||
|
||||
label = "Load as Arnold standin"
|
||||
order = -5
|
||||
|
|
|
|||
108
openpype/hosts/maya/plugins/load/load_maya_usd.py
Normal file
108
openpype/hosts/maya/plugins/load/load_maya_usd.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import maya.cmds as cmds
|
||||
|
||||
from openpype.pipeline import (
|
||||
load,
|
||||
get_representation_path,
|
||||
)
|
||||
from openpype.pipeline.load import get_representation_path_from_context
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
namespaced,
|
||||
unique_namespace
|
||||
)
|
||||
from openpype.hosts.maya.api.pipeline import containerise
|
||||
|
||||
|
||||
class MayaUsdLoader(load.LoaderPlugin):
|
||||
"""Read USD data in a Maya USD Proxy"""
|
||||
|
||||
families = ["model", "usd", "pointcache", "animation"]
|
||||
representations = ["usd", "usda", "usdc", "usdz", "abc"]
|
||||
|
||||
label = "Load USD to Maya Proxy"
|
||||
order = -1
|
||||
icon = "code-fork"
|
||||
color = "orange"
|
||||
|
||||
def load(self, context, name=None, namespace=None, options=None):
|
||||
asset = context['asset']['name']
|
||||
namespace = namespace or unique_namespace(
|
||||
asset + "_",
|
||||
prefix="_" if asset[0].isdigit() else "",
|
||||
suffix="_",
|
||||
)
|
||||
|
||||
# Make sure we can load the plugin
|
||||
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
|
||||
|
||||
path = get_representation_path_from_context(context)
|
||||
|
||||
# Create the shape
|
||||
cmds.namespace(addNamespace=namespace)
|
||||
with namespaced(namespace, new=False):
|
||||
transform = cmds.createNode("transform",
|
||||
name=name,
|
||||
skipSelect=True)
|
||||
proxy = cmds.createNode('mayaUsdProxyShape',
|
||||
name="{}Shape".format(name),
|
||||
parent=transform,
|
||||
skipSelect=True)
|
||||
|
||||
cmds.connectAttr("time1.outTime", "{}.time".format(proxy))
|
||||
cmds.setAttr("{}.filePath".format(proxy), path, type="string")
|
||||
|
||||
# By default, we force the proxy to not use a shared stage because
|
||||
# when doing so Maya will quite easily allow to save into the
|
||||
# loaded usd file. Since we are loading published files we want to
|
||||
# avoid altering them. Unshared stages also save their edits into
|
||||
# the workfile as an artist might expect it to do.
|
||||
cmds.setAttr("{}.shareStage".format(proxy), False)
|
||||
# cmds.setAttr("{}.shareStage".format(proxy), lock=True)
|
||||
|
||||
nodes = [transform, proxy]
|
||||
self[:] = nodes
|
||||
|
||||
return containerise(
|
||||
name=name,
|
||||
namespace=namespace,
|
||||
nodes=nodes,
|
||||
context=context,
|
||||
loader=self.__class__.__name__)
|
||||
|
||||
def update(self, container, representation):
|
||||
# type: (dict, dict) -> None
|
||||
"""Update container with specified representation."""
|
||||
node = container['objectName']
|
||||
assert cmds.objExists(node), "Missing container"
|
||||
|
||||
members = cmds.sets(node, query=True) or []
|
||||
shapes = cmds.ls(members, type="mayaUsdProxyShape")
|
||||
|
||||
path = get_representation_path(representation)
|
||||
for shape in shapes:
|
||||
cmds.setAttr("{}.filePath".format(shape), path, type="string")
|
||||
|
||||
cmds.setAttr("{}.representation".format(node),
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
||||
def remove(self, container):
|
||||
# type: (dict) -> None
|
||||
"""Remove loaded container."""
|
||||
# Delete container and its contents
|
||||
if cmds.objExists(container['objectName']):
|
||||
members = cmds.sets(container['objectName'], query=True) or []
|
||||
cmds.delete([container['objectName']] + members)
|
||||
|
||||
# Remove the namespace, if empty
|
||||
namespace = container['namespace']
|
||||
if cmds.namespace(exists=namespace):
|
||||
members = cmds.namespaceInfo(namespace, listNamespace=True)
|
||||
if not members:
|
||||
cmds.namespace(removeNamespace=namespace)
|
||||
else:
|
||||
self.log.warning("Namespace not deleted because it "
|
||||
"still has members: %s", namespace)
|
||||
|
|
@ -58,17 +58,3 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin):
|
|||
if instance.data.get("farm"):
|
||||
instance.data["families"].append("publish.farm")
|
||||
|
||||
# Collect user defined attributes.
|
||||
if not instance.data.get("includeUserDefinedAttributes", False):
|
||||
return
|
||||
|
||||
user_defined_attributes = set()
|
||||
for node in hierarchy:
|
||||
attrs = cmds.listAttr(node, userDefined=True) or list()
|
||||
shapes = cmds.listRelatives(node, shapes=True) or list()
|
||||
for shape in shapes:
|
||||
attrs.extend(cmds.listAttr(shape, userDefined=True) or list())
|
||||
|
||||
user_defined_attributes.update(attrs)
|
||||
|
||||
instance.data["userDefinedAttributes"] = list(user_defined_attributes)
|
||||
|
|
|
|||
|
|
@ -17,11 +17,6 @@ SHAPE_ATTRS = ["castsShadows",
|
|||
"visibleInRefractions",
|
||||
"doubleSided",
|
||||
"opposite"]
|
||||
|
||||
RENDERER_NODE_TYPES = [
|
||||
# redshift
|
||||
"RedshiftMeshParameters"
|
||||
]
|
||||
SHAPE_ATTRS = set(SHAPE_ATTRS)
|
||||
|
||||
|
||||
|
|
@ -36,12 +31,13 @@ def get_pxr_multitexture_file_attrs(node):
|
|||
|
||||
|
||||
FILE_NODES = {
|
||||
# maya
|
||||
"file": "fileTextureName",
|
||||
|
||||
# arnold (mtoa)
|
||||
"aiImage": "filename",
|
||||
|
||||
# redshift
|
||||
"RedshiftNormalMap": "tex0",
|
||||
|
||||
# renderman
|
||||
"PxrBump": "filename",
|
||||
"PxrNormalMap": "filename",
|
||||
"PxrMultiTexture": get_pxr_multitexture_file_attrs,
|
||||
|
|
@ -49,6 +45,22 @@ FILE_NODES = {
|
|||
"PxrTexture": "filename"
|
||||
}
|
||||
|
||||
# Keep only node types that actually exist
|
||||
all_node_types = set(cmds.allNodeTypes())
|
||||
for node_type in list(FILE_NODES.keys()):
|
||||
if node_type not in all_node_types:
|
||||
FILE_NODES.pop(node_type)
|
||||
del all_node_types
|
||||
|
||||
# Cache pixar dependency node types so we can perform a type lookup against it
|
||||
PXR_NODES = set()
|
||||
if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True):
|
||||
PXR_NODES = set(
|
||||
cmds.pluginInfo("RenderMan_for_Maya",
|
||||
query=True,
|
||||
dependNode=True)
|
||||
)
|
||||
|
||||
|
||||
def get_attributes(dictionary, attr, node=None):
|
||||
# type: (dict, str, str) -> list
|
||||
|
|
@ -232,20 +244,17 @@ def get_file_node_files(node):
|
|||
|
||||
"""
|
||||
paths = get_file_node_paths(node)
|
||||
sequences = []
|
||||
replaces = []
|
||||
|
||||
# For sequences get all files and filter to only existing files
|
||||
result = []
|
||||
for index, path in enumerate(paths):
|
||||
if node_uses_image_sequence(node, path):
|
||||
glob_pattern = seq_to_glob(path)
|
||||
sequences.extend(glob.glob(glob_pattern))
|
||||
replaces.append(index)
|
||||
result.extend(glob.glob(glob_pattern))
|
||||
elif os.path.exists(path):
|
||||
result.append(path)
|
||||
|
||||
for index in replaces:
|
||||
paths.pop(index)
|
||||
|
||||
paths.extend(sequences)
|
||||
|
||||
return [p for p in paths if os.path.exists(p)]
|
||||
return result
|
||||
|
||||
|
||||
class CollectLook(pyblish.api.InstancePlugin):
|
||||
|
|
@ -260,7 +269,7 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
membership relations.
|
||||
|
||||
Collects:
|
||||
lookAttribtutes (list): Nodes in instance with their altered attributes
|
||||
lookAttributes (list): Nodes in instance with their altered attributes
|
||||
lookSetRelations (list): Sets and their memberships
|
||||
lookSets (list): List of set names included in the look
|
||||
|
||||
|
|
@ -286,7 +295,10 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
|
||||
"""
|
||||
self.log.debug("Looking for look associations "
|
||||
"for %s" % instance.data['name'])
|
||||
"for %s" % instance.data['name'])
|
||||
|
||||
# Lookup set (optimization)
|
||||
instance_lookup = set(cmds.ls(instance, long=True))
|
||||
|
||||
# Discover related object sets
|
||||
self.log.debug("Gathering sets ...")
|
||||
|
|
@ -296,65 +308,20 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
instance_lookup = set(cmds.ls(instance, long=True))
|
||||
|
||||
self.log.debug("Gathering set relations ...")
|
||||
# Ensure iteration happen in a list so we can remove keys from the
|
||||
# Ensure iteration happen in a list to allow removing keys from the
|
||||
# dict within the loop
|
||||
|
||||
# skipped types of attribute on render specific nodes
|
||||
disabled_types = ["message", "TdataCompound"]
|
||||
|
||||
for obj_set in list(sets):
|
||||
self.log.debug("From {}".format(obj_set))
|
||||
|
||||
# if node is specified as renderer node type, it will be
|
||||
# serialized with its attributes.
|
||||
if cmds.nodeType(obj_set) in RENDERER_NODE_TYPES:
|
||||
self.log.debug("- {} is {}".format(
|
||||
obj_set, cmds.nodeType(obj_set)))
|
||||
|
||||
node_attrs = []
|
||||
|
||||
# serialize its attributes so they can be recreated on look
|
||||
# load.
|
||||
for attr in cmds.listAttr(obj_set):
|
||||
# skip publishedNodeInfo attributes as they break
|
||||
# getAttr() and we don't need them anyway
|
||||
if attr.startswith("publishedNodeInfo"):
|
||||
continue
|
||||
|
||||
# skip attributes types defined in 'disabled_type' list
|
||||
if cmds.getAttr("{}.{}".format(obj_set, attr), type=True) in disabled_types: # noqa
|
||||
continue
|
||||
|
||||
node_attrs.append((
|
||||
attr,
|
||||
cmds.getAttr("{}.{}".format(obj_set, attr)),
|
||||
cmds.getAttr(
|
||||
"{}.{}".format(obj_set, attr), type=True)
|
||||
))
|
||||
|
||||
for member in cmds.ls(
|
||||
cmds.sets(obj_set, query=True), long=True):
|
||||
member_data = self.collect_member_data(member,
|
||||
instance_lookup)
|
||||
if not member_data:
|
||||
continue
|
||||
|
||||
# Add information of the node to the members list
|
||||
sets[obj_set]["members"].append(member_data)
|
||||
|
||||
# Get all nodes of the current objectSet (shadingEngine)
|
||||
for member in cmds.ls(cmds.sets(obj_set, query=True), long=True):
|
||||
member_data = self.collect_member_data(member,
|
||||
instance_lookup)
|
||||
if not member_data:
|
||||
continue
|
||||
|
||||
# Add information of the node to the members list
|
||||
sets[obj_set]["members"].append(member_data)
|
||||
if member_data:
|
||||
# Add information of the node to the members list
|
||||
sets[obj_set]["members"].append(member_data)
|
||||
|
||||
# Remove sets that didn't have any members assigned in the end
|
||||
# Thus the data will be limited to only what we need.
|
||||
self.log.debug("obj_set {}".format(sets[obj_set]))
|
||||
if not sets[obj_set]["members"]:
|
||||
self.log.debug(
|
||||
"Removing redundant set information: {}".format(obj_set)
|
||||
|
|
@ -383,35 +350,28 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
"rman__displacement"
|
||||
]
|
||||
if look_sets:
|
||||
materials = []
|
||||
self.log.debug("Found look sets: {}".format(look_sets))
|
||||
|
||||
# Get all material attrs for all look sets to retrieve their inputs
|
||||
existing_attrs = []
|
||||
for look in look_sets:
|
||||
for at in shader_attrs:
|
||||
try:
|
||||
con = cmds.listConnections("{}.{}".format(look, at))
|
||||
except ValueError:
|
||||
# skip attributes that are invalid in current
|
||||
# context. For example in the case where
|
||||
# Arnold is not enabled.
|
||||
continue
|
||||
if con:
|
||||
materials.extend(con)
|
||||
for attr in shader_attrs:
|
||||
if cmds.attributeQuery(attr, node=look, exists=True):
|
||||
existing_attrs.append("{}.{}".format(look, attr))
|
||||
materials = cmds.listConnections(existing_attrs,
|
||||
source=True,
|
||||
destination=False) or []
|
||||
|
||||
self.log.debug("Found materials:\n{}".format(materials))
|
||||
|
||||
self.log.debug("Found the following sets:\n{}".format(look_sets))
|
||||
# Get the entire node chain of the look sets
|
||||
# history = cmds.listHistory(look_sets)
|
||||
history = []
|
||||
for material in materials:
|
||||
history.extend(cmds.listHistory(material, ac=True))
|
||||
|
||||
# handle VrayPluginNodeMtl node - see #1397
|
||||
vray_plugin_nodes = cmds.ls(
|
||||
history, type="VRayPluginNodeMtl", long=True)
|
||||
for vray_node in vray_plugin_nodes:
|
||||
history.extend(cmds.listHistory(vray_node, ac=True))
|
||||
# history = cmds.listHistory(look_sets, allConnections=True)
|
||||
history = cmds.listHistory(materials, allConnections=True)
|
||||
|
||||
# Since we retrieved history only of the connected materials
|
||||
# connected to the look sets above we now add direct history
|
||||
# for some of the look sets directly
|
||||
# handling render attribute sets
|
||||
render_set_types = [
|
||||
"VRayDisplacement",
|
||||
|
|
@ -429,20 +389,26 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
or []
|
||||
)
|
||||
|
||||
all_supported_nodes = FILE_NODES.keys()
|
||||
files = []
|
||||
for node_type in all_supported_nodes:
|
||||
files.extend(cmds.ls(history, type=node_type, long=True))
|
||||
# Ensure unique entries only
|
||||
history = list(set(history))
|
||||
|
||||
files = cmds.ls(history,
|
||||
# It's important only node types are passed that
|
||||
# exist (e.g. for loaded plugins) because otherwise
|
||||
# the result will turn back empty
|
||||
type=list(FILE_NODES.keys()),
|
||||
long=True)
|
||||
|
||||
# Sort for log readability
|
||||
files.sort()
|
||||
|
||||
self.log.debug("Collected file nodes:\n{}".format(files))
|
||||
# Collect textures if any file nodes are found
|
||||
instance.data["resources"] = []
|
||||
for n in files:
|
||||
for res in self.collect_resources(n):
|
||||
instance.data["resources"].append(res)
|
||||
|
||||
self.log.debug("Collected resources: {}".format(
|
||||
instance.data["resources"]))
|
||||
resources = []
|
||||
for node in files: # sort for log readability
|
||||
resources.extend(self.collect_resources(node))
|
||||
instance.data["resources"] = resources
|
||||
self.log.debug("Collected resources: {}".format(resources))
|
||||
|
||||
# Log warning when no relevant sets were retrieved for the look.
|
||||
if (
|
||||
|
|
@ -537,14 +503,14 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
# Collect changes to "custom" attributes
|
||||
node_attrs = get_look_attrs(node)
|
||||
|
||||
self.log.debug(
|
||||
"Node \"{0}\" attributes: {1}".format(node, node_attrs)
|
||||
)
|
||||
|
||||
# Only include if there are any properties we care about
|
||||
if not node_attrs:
|
||||
continue
|
||||
|
||||
self.log.debug(
|
||||
"Node \"{0}\" attributes: {1}".format(node, node_attrs)
|
||||
)
|
||||
|
||||
node_attributes = {}
|
||||
for attr in node_attrs:
|
||||
if not cmds.attributeQuery(attr, node=node, exists=True):
|
||||
|
|
@ -575,14 +541,14 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
Returns:
|
||||
dict
|
||||
"""
|
||||
self.log.debug("processing: {}".format(node))
|
||||
all_supported_nodes = FILE_NODES.keys()
|
||||
if cmds.nodeType(node) not in all_supported_nodes:
|
||||
if cmds.nodeType(node) not in FILE_NODES:
|
||||
self.log.error(
|
||||
"Unsupported file node: {}".format(cmds.nodeType(node)))
|
||||
raise AssertionError("Unsupported file node")
|
||||
|
||||
self.log.debug(" - got {}".format(cmds.nodeType(node)))
|
||||
self.log.debug(
|
||||
"Collecting resource: {} ({})".format(node, cmds.nodeType(node))
|
||||
)
|
||||
|
||||
attributes = get_attributes(FILE_NODES, cmds.nodeType(node), node)
|
||||
for attribute in attributes:
|
||||
|
|
@ -590,9 +556,6 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
node,
|
||||
attribute
|
||||
))
|
||||
computed_attribute = "{}.{}".format(node, attribute)
|
||||
if attribute == "fileTextureName":
|
||||
computed_attribute = node + ".computedFileTextureNamePattern"
|
||||
|
||||
self.log.debug(" - file source: {}".format(source))
|
||||
color_space_attr = "{}.colorSpace".format(node)
|
||||
|
|
@ -601,27 +564,25 @@ class CollectLook(pyblish.api.InstancePlugin):
|
|||
except ValueError:
|
||||
# node doesn't have colorspace attribute
|
||||
color_space = "Raw"
|
||||
|
||||
# Compare with the computed file path, e.g. the one with
|
||||
# the <UDIM> pattern in it, to generate some logging information
|
||||
# about this difference
|
||||
computed_source = cmds.getAttr(computed_attribute)
|
||||
if source != computed_source:
|
||||
self.log.debug("Detected computed file pattern difference "
|
||||
"from original pattern: {0} "
|
||||
"({1} -> {2})".format(node,
|
||||
source,
|
||||
computed_source))
|
||||
# Only for file nodes with `fileTextureName` attribute
|
||||
if attribute == "fileTextureName":
|
||||
computed_source = cmds.getAttr(
|
||||
"{}.computedFileTextureNamePattern".format(node)
|
||||
)
|
||||
if source != computed_source:
|
||||
self.log.debug("Detected computed file pattern difference "
|
||||
"from original pattern: {0} "
|
||||
"({1} -> {2})".format(node,
|
||||
source,
|
||||
computed_source))
|
||||
|
||||
# renderman allows nodes to have filename attribute empty while
|
||||
# you can have another incoming connection from different node.
|
||||
pxr_nodes = set()
|
||||
if cmds.pluginInfo("RenderMan_for_Maya", query=True, loaded=True):
|
||||
pxr_nodes = set(
|
||||
cmds.pluginInfo("RenderMan_for_Maya",
|
||||
query=True,
|
||||
dependNode=True)
|
||||
)
|
||||
if not source and cmds.nodeType(node) in pxr_nodes:
|
||||
if not source and cmds.nodeType(node) in PXR_NODES:
|
||||
self.log.debug("Renderman: source is empty, skipping...")
|
||||
continue
|
||||
# We replace backslashes with forward slashes because V-Ray
|
||||
|
|
|
|||
|
|
@ -45,18 +45,3 @@ class CollectPointcache(pyblish.api.InstancePlugin):
|
|||
if proxy_set:
|
||||
instance.remove(proxy_set)
|
||||
instance.data["setMembers"].remove(proxy_set)
|
||||
|
||||
# Collect user defined attributes.
|
||||
if not instance.data.get("includeUserDefinedAttributes", False):
|
||||
return
|
||||
|
||||
user_defined_attributes = set()
|
||||
for node in instance:
|
||||
attrs = cmds.listAttr(node, userDefined=True) or list()
|
||||
shapes = cmds.listRelatives(node, shapes=True) or list()
|
||||
for shape in shapes:
|
||||
attrs.extend(cmds.listAttr(shape, userDefined=True) or list())
|
||||
|
||||
user_defined_attributes.update(attrs)
|
||||
|
||||
instance.data["userDefinedAttributes"] = list(user_defined_attributes)
|
||||
|
|
|
|||
|
|
@ -157,10 +157,10 @@ class CollectMayaRender(pyblish.api.InstancePlugin):
|
|||
|
||||
# append full path
|
||||
aov_dict = {}
|
||||
default_render_folder = context.data.get("project_settings")\
|
||||
.get("maya")\
|
||||
.get("RenderSettings")\
|
||||
.get("default_render_image_folder") or ""
|
||||
image_directory = os.path.join(
|
||||
cmds.workspace(query=True, rootDirectory=True),
|
||||
cmds.workspace(fileRuleEntry="images")
|
||||
)
|
||||
# replace relative paths with absolute. Render products are
|
||||
# returned as list of dictionaries.
|
||||
publish_meta_path = None
|
||||
|
|
@ -168,8 +168,7 @@ class CollectMayaRender(pyblish.api.InstancePlugin):
|
|||
full_paths = []
|
||||
aov_first_key = list(aov.keys())[0]
|
||||
for file in aov[aov_first_key]:
|
||||
full_path = os.path.join(workspace, default_render_folder,
|
||||
file)
|
||||
full_path = os.path.join(image_directory, file)
|
||||
full_path = full_path.replace("\\", "/")
|
||||
full_paths.append(full_path)
|
||||
publish_meta_path = os.path.dirname(full_path)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectUserDefinedAttributes(pyblish.api.InstancePlugin):
|
||||
"""Collect user defined attributes for nodes in instance."""
|
||||
|
||||
order = pyblish.api.CollectorOrder + 0.45
|
||||
families = ["pointcache", "animation", "usd"]
|
||||
label = "Collect User Defined Attributes"
|
||||
hosts = ["maya"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
# Collect user defined attributes.
|
||||
if not instance.data.get("includeUserDefinedAttributes", False):
|
||||
return
|
||||
|
||||
if "out_hierarchy" in instance.data:
|
||||
# animation family
|
||||
nodes = instance.data["out_hierarchy"]
|
||||
else:
|
||||
nodes = instance[:]
|
||||
if not nodes:
|
||||
return
|
||||
|
||||
shapes = cmds.listRelatives(nodes, shapes=True, fullPath=True) or []
|
||||
nodes = set(nodes).union(shapes)
|
||||
|
||||
attrs = cmds.listAttr(list(nodes), userDefined=True) or []
|
||||
user_defined_attributes = list(sorted(set(attrs)))
|
||||
instance.data["userDefinedAttributes"] = user_defined_attributes
|
||||
|
||||
self.log.debug(
|
||||
"Collected user defined attributes: {}".format(
|
||||
", ".join(user_defined_attributes)
|
||||
)
|
||||
)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import maya.api.OpenMaya as om
|
||||
import maya.api.OpenMayaUI as omui
|
||||
|
||||
import pyblish.api
|
||||
import tempfile
|
||||
|
||||
from openpype.hosts.maya.api.lib import IS_HEADLESS
|
||||
|
||||
|
||||
class ExtractActiveViewThumbnail(pyblish.api.InstancePlugin):
|
||||
"""Set instance thumbnail to a screengrab of current active viewport.
|
||||
|
||||
This makes it so that if an instance does not have a thumbnail set yet that
|
||||
it will get a thumbnail of the currently active view at the time of
|
||||
publishing as a fallback.
|
||||
|
||||
"""
|
||||
order = pyblish.api.ExtractorOrder + 0.49
|
||||
label = "Active View Thumbnail"
|
||||
families = ["workfile"]
|
||||
hosts = ["maya"]
|
||||
|
||||
def process(self, instance):
|
||||
if IS_HEADLESS:
|
||||
self.log.debug(
|
||||
"Skip extraction of active view thumbnail, due to being in"
|
||||
"headless mode."
|
||||
)
|
||||
return
|
||||
|
||||
thumbnail = instance.data.get("thumbnailPath")
|
||||
if not thumbnail:
|
||||
view_thumbnail = self.get_view_thumbnail(instance)
|
||||
if not view_thumbnail:
|
||||
return
|
||||
|
||||
self.log.debug("Setting instance thumbnail path to: {}".format(
|
||||
view_thumbnail
|
||||
))
|
||||
instance.data["thumbnailPath"] = view_thumbnail
|
||||
|
||||
def get_view_thumbnail(self, instance):
|
||||
cache_key = "__maya_view_thumbnail"
|
||||
context = instance.context
|
||||
|
||||
if cache_key not in context.data:
|
||||
# Generate only a single thumbnail, even for multiple instances
|
||||
with tempfile.NamedTemporaryFile(suffix="_thumbnail.jpg",
|
||||
delete=False) as f:
|
||||
path = f.name
|
||||
|
||||
view = omui.M3dView.active3dView()
|
||||
image = om.MImage()
|
||||
view.readColorBuffer(image, True)
|
||||
image.writeToFile(path, "jpg")
|
||||
self.log.debug("Generated thumbnail: {}".format(path))
|
||||
|
||||
context.data["cleanupFullPaths"].append(path)
|
||||
context.data[cache_key] = path
|
||||
return context.data[cache_key]
|
||||
293
openpype/hosts/maya/plugins/publish/extract_maya_usd.py
Normal file
293
openpype/hosts/maya/plugins/publish/extract_maya_usd.py
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
import os
|
||||
import six
|
||||
import json
|
||||
import contextlib
|
||||
|
||||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
from openpype.pipeline import publish
|
||||
from openpype.hosts.maya.api.lib import maintained_selection
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def usd_export_attributes(nodes, attrs=None, attr_prefixes=None, mapping=None):
|
||||
"""Define attributes for the given nodes that should be exported.
|
||||
|
||||
MayaUSDExport will export custom attributes if the Maya node has a
|
||||
string attribute `USD_UserExportedAttributesJson` that provides an
|
||||
export mapping for the maya attributes. This context manager will try
|
||||
to autogenerate such an attribute during the export to include attributes
|
||||
for the export.
|
||||
|
||||
Arguments:
|
||||
nodes (List[str]): Nodes to process.
|
||||
attrs (Optional[List[str]]): Full name of attributes to include.
|
||||
attr_prefixes (Optional[List[str]]): Prefixes of attributes to include.
|
||||
mapping (Optional[Dict[Dict]]): A mapping per attribute name for the
|
||||
conversion to a USD attribute, including renaming, defining type,
|
||||
converting attribute precision, etc. This match the usual
|
||||
`USD_UserExportedAttributesJson` json mapping of `mayaUSDExport`.
|
||||
When no mapping provided for an attribute it will use `{}` as
|
||||
value.
|
||||
|
||||
Examples:
|
||||
>>> with usd_export_attributes(
|
||||
>>> ["pCube1"], attrs="myDoubleAttributeAsFloat", mapping={
|
||||
>>> "myDoubleAttributeAsFloat": {
|
||||
>>> "usdAttrName": "my:namespace:attrib",
|
||||
>>> "translateMayaDoubleToUsdSinglePrecision": True,
|
||||
>>> }
|
||||
>>> })
|
||||
|
||||
"""
|
||||
# todo: this might be better done with a custom export chaser
|
||||
# see `chaser` argument for `mayaUSDExport`
|
||||
|
||||
import maya.api.OpenMaya as om
|
||||
|
||||
if not attrs and not attr_prefixes:
|
||||
# context manager does nothing
|
||||
yield
|
||||
return
|
||||
|
||||
if attrs is None:
|
||||
attrs = []
|
||||
if attr_prefixes is None:
|
||||
attr_prefixes = []
|
||||
if mapping is None:
|
||||
mapping = {}
|
||||
|
||||
usd_json_attr = "USD_UserExportedAttributesJson"
|
||||
strings = attrs + ["{}*".format(prefix) for prefix in attr_prefixes]
|
||||
context_state = {}
|
||||
for node in set(nodes):
|
||||
node_attrs = cmds.listAttr(node, st=strings)
|
||||
if not node_attrs:
|
||||
# Nothing to do for this node
|
||||
continue
|
||||
|
||||
node_attr_data = {}
|
||||
for node_attr in set(node_attrs):
|
||||
node_attr_data[node_attr] = mapping.get(node_attr, {})
|
||||
|
||||
if cmds.attributeQuery(usd_json_attr, node=node, exists=True):
|
||||
existing_node_attr_value = cmds.getAttr(
|
||||
"{}.{}".format(node, usd_json_attr)
|
||||
)
|
||||
if existing_node_attr_value and existing_node_attr_value != "{}":
|
||||
# Any existing attribute mappings in an existing
|
||||
# `USD_UserExportedAttributesJson` attribute always take
|
||||
# precedence over what this function tries to imprint
|
||||
existing_node_attr_data = json.loads(existing_node_attr_value)
|
||||
node_attr_data.update(existing_node_attr_data)
|
||||
|
||||
context_state[node] = json.dumps(node_attr_data)
|
||||
|
||||
sel = om.MSelectionList()
|
||||
dg_mod = om.MDGModifier()
|
||||
fn_string = om.MFnStringData()
|
||||
fn_typed = om.MFnTypedAttribute()
|
||||
try:
|
||||
for node, value in context_state.items():
|
||||
data = fn_string.create(value)
|
||||
sel.clear()
|
||||
if cmds.attributeQuery(usd_json_attr, node=node, exists=True):
|
||||
# Set the attribute value
|
||||
sel.add("{}.{}".format(node, usd_json_attr))
|
||||
plug = sel.getPlug(0)
|
||||
dg_mod.newPlugValue(plug, data)
|
||||
else:
|
||||
# Create attribute with the value as default value
|
||||
sel.add(node)
|
||||
node_obj = sel.getDependNode(0)
|
||||
attr_obj = fn_typed.create(usd_json_attr,
|
||||
usd_json_attr,
|
||||
om.MFnData.kString,
|
||||
data)
|
||||
dg_mod.addAttribute(node_obj, attr_obj)
|
||||
dg_mod.doIt()
|
||||
yield
|
||||
finally:
|
||||
dg_mod.undoIt()
|
||||
|
||||
|
||||
class ExtractMayaUsd(publish.Extractor):
|
||||
"""Extractor for Maya USD Asset data.
|
||||
|
||||
Upon publish a .usd (or .usdz) asset file will typically be written.
|
||||
"""
|
||||
|
||||
label = "Extract Maya USD Asset"
|
||||
hosts = ["maya"]
|
||||
families = ["mayaUsd"]
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
"""Overridable options for Maya USD Export
|
||||
|
||||
Given in the following format
|
||||
- {NAME: EXPECTED TYPE}
|
||||
|
||||
If the overridden option's type does not match,
|
||||
the option is not included and a warning is logged.
|
||||
|
||||
"""
|
||||
|
||||
# TODO: Support more `mayaUSDExport` parameters
|
||||
return {
|
||||
"defaultUSDFormat": str,
|
||||
"stripNamespaces": bool,
|
||||
"mergeTransformAndShape": bool,
|
||||
"exportDisplayColor": bool,
|
||||
"exportColorSets": bool,
|
||||
"exportInstances": bool,
|
||||
"exportUVs": bool,
|
||||
"exportVisibility": bool,
|
||||
"exportComponentTags": bool,
|
||||
"exportRefsAsInstanceable": bool,
|
||||
"eulerFilter": bool,
|
||||
"renderableOnly": bool,
|
||||
"jobContext": (list, None) # optional list
|
||||
# "worldspace": bool,
|
||||
}
|
||||
|
||||
@property
|
||||
def default_options(self):
|
||||
"""The default options for Maya USD Export."""
|
||||
|
||||
# TODO: Support more `mayaUSDExport` parameters
|
||||
return {
|
||||
"defaultUSDFormat": "usdc",
|
||||
"stripNamespaces": False,
|
||||
"mergeTransformAndShape": False,
|
||||
"exportDisplayColor": False,
|
||||
"exportColorSets": True,
|
||||
"exportInstances": True,
|
||||
"exportUVs": True,
|
||||
"exportVisibility": True,
|
||||
"exportComponentTags": True,
|
||||
"exportRefsAsInstanceable": False,
|
||||
"eulerFilter": True,
|
||||
"renderableOnly": False,
|
||||
"jobContext": None
|
||||
# "worldspace": False
|
||||
}
|
||||
|
||||
def parse_overrides(self, instance, options):
|
||||
"""Inspect data of instance to determine overridden options"""
|
||||
|
||||
for key in instance.data:
|
||||
if key not in self.options:
|
||||
continue
|
||||
|
||||
# Ensure the data is of correct type
|
||||
value = instance.data[key]
|
||||
if isinstance(value, six.text_type):
|
||||
value = str(value)
|
||||
if not isinstance(value, self.options[key]):
|
||||
self.log.warning(
|
||||
"Overridden attribute {key} was of "
|
||||
"the wrong type: {invalid_type} "
|
||||
"- should have been {valid_type}".format(
|
||||
key=key,
|
||||
invalid_type=type(value).__name__,
|
||||
valid_type=self.options[key].__name__))
|
||||
continue
|
||||
|
||||
options[key] = value
|
||||
|
||||
return options
|
||||
|
||||
def filter_members(self, members):
|
||||
# Can be overridden by inherited classes
|
||||
return members
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
# Load plugin first
|
||||
cmds.loadPlugin("mayaUsdPlugin", quiet=True)
|
||||
|
||||
# Define output file path
|
||||
staging_dir = self.staging_dir(instance)
|
||||
file_name = "{0}.usd".format(instance.name)
|
||||
file_path = os.path.join(staging_dir, file_name)
|
||||
file_path = file_path.replace('\\', '/')
|
||||
|
||||
# Parse export options
|
||||
options = self.default_options
|
||||
options = self.parse_overrides(instance, options)
|
||||
self.log.debug("Export options: {0}".format(options))
|
||||
|
||||
# Perform extraction
|
||||
self.log.debug("Performing extraction ...")
|
||||
|
||||
members = instance.data("setMembers")
|
||||
self.log.debug('Collected objects: {}'.format(members))
|
||||
members = self.filter_members(members)
|
||||
if not members:
|
||||
self.log.error('No members!')
|
||||
return
|
||||
|
||||
start = instance.data["frameStartHandle"]
|
||||
end = instance.data["frameEndHandle"]
|
||||
|
||||
def parse_attr_str(attr_str):
|
||||
result = list()
|
||||
for attr in attr_str.split(","):
|
||||
attr = attr.strip()
|
||||
if not attr:
|
||||
continue
|
||||
result.append(attr)
|
||||
return result
|
||||
|
||||
attrs = parse_attr_str(instance.data.get("attr", ""))
|
||||
attrs += instance.data.get("userDefinedAttributes", [])
|
||||
attrs += ["cbId"]
|
||||
attr_prefixes = parse_attr_str(instance.data.get("attrPrefix", ""))
|
||||
|
||||
self.log.debug('Exporting USD: {} / {}'.format(file_path, members))
|
||||
with maintained_selection():
|
||||
with usd_export_attributes(instance[:],
|
||||
attrs=attrs,
|
||||
attr_prefixes=attr_prefixes):
|
||||
cmds.mayaUSDExport(file=file_path,
|
||||
frameRange=(start, end),
|
||||
frameStride=instance.data.get("step", 1.0),
|
||||
exportRoots=members,
|
||||
**options)
|
||||
|
||||
representation = {
|
||||
'name': "usd",
|
||||
'ext': "usd",
|
||||
'files': file_name,
|
||||
'stagingDir': staging_dir
|
||||
}
|
||||
instance.data.setdefault("representations", []).append(representation)
|
||||
|
||||
self.log.debug(
|
||||
"Extracted instance {} to {}".format(instance.name, file_path)
|
||||
)
|
||||
|
||||
|
||||
class ExtractMayaUsdAnim(ExtractMayaUsd):
|
||||
"""Extractor for Maya USD Animation Sparse Cache data.
|
||||
|
||||
This will extract the sparse cache data from the scene and generate a
|
||||
USD file with all the animation data.
|
||||
|
||||
Upon publish a .usd sparse cache will be written.
|
||||
"""
|
||||
label = "Extract Maya USD Animation Sparse Cache"
|
||||
families = ["animation", "mayaUsd"]
|
||||
match = pyblish.api.Subset
|
||||
|
||||
def filter_members(self, members):
|
||||
out_set = next((i for i in members if i.endswith("out_SET")), None)
|
||||
|
||||
if out_set is None:
|
||||
self.log.warning("Expecting out_SET")
|
||||
return None
|
||||
|
||||
members = cmds.ls(cmds.sets(out_set, query=True), long=True)
|
||||
return members
|
||||
|
|
@ -28,7 +28,7 @@ class ExtractMultiverseUsd(publish.Extractor):
|
|||
|
||||
label = "Extract Multiverse USD Asset"
|
||||
hosts = ["maya"]
|
||||
families = ["usd"]
|
||||
families = ["mvUsd"]
|
||||
scene_type = "usd"
|
||||
file_formats = ["usd", "usda", "usdz"]
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ class ExtractAlembic(publish.Extractor):
|
|||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
instance.context.data["cleanupFullPaths"].append(path)
|
||||
if not instance.data.get("stagingDir_persistent", False):
|
||||
instance.context.data["cleanupFullPaths"].append(path)
|
||||
|
||||
self.log.debug("Extracted {} to {}".format(instance, dirname))
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ class ExtractProxyAlembic(publish.Extractor):
|
|||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
instance.context.data["cleanupFullPaths"].append(path)
|
||||
if not instance.data.get("stagingDir_persistent", False):
|
||||
instance.context.data["cleanupFullPaths"].append(path)
|
||||
|
||||
self.log.debug("Extracted {} to {}".format(instance, dirname))
|
||||
# remove the bounding box
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ class ExtractThumbnail(publish.Extractor):
|
|||
"Create temp directory {} for thumbnail".format(dst_staging)
|
||||
)
|
||||
# Store new staging to cleanup paths
|
||||
instance.context.data["cleanupFullPaths"].append(dst_staging)
|
||||
filename = "{0}".format(instance.name)
|
||||
path = os.path.join(dst_staging, filename)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from maya import cmds
|
||||
|
||||
from openpype.pipeline.publish import (
|
||||
|
|
@ -22,8 +25,12 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
|
||||
required_images_rule = self.get_default_render_image_folder(instance)
|
||||
current_images_rule = cmds.workspace(fileRuleEntry="images")
|
||||
required_images_rule = os.path.normpath(
|
||||
self.get_default_render_image_folder(instance)
|
||||
)
|
||||
current_images_rule = os.path.normpath(
|
||||
cmds.workspace(fileRuleEntry="images")
|
||||
)
|
||||
|
||||
if current_images_rule != required_images_rule:
|
||||
raise PublishValidationError(
|
||||
|
|
@ -42,8 +49,17 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin):
|
|||
cmds.workspace(fileRule=("images", required_images_rule))
|
||||
cmds.workspace(saveWorkspace=True)
|
||||
|
||||
@staticmethod
|
||||
def get_default_render_image_folder(instance):
|
||||
@classmethod
|
||||
def get_default_render_image_folder(cls, instance):
|
||||
staging_dir = instance.data.get("stagingDir")
|
||||
if staging_dir:
|
||||
cls.log.debug(
|
||||
"Staging dir found: \"{}\". Ignoring setting from "
|
||||
"`project_settings/maya/RenderSettings/"
|
||||
"default_render_image_folder`.".format(staging_dir)
|
||||
)
|
||||
return staging_dir
|
||||
|
||||
return instance.context.data.get('project_settings')\
|
||||
.get('maya') \
|
||||
.get('RenderSettings') \
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from openpype.client import get_last_version_by_subset_name
|
|||
from openpype.hosts.maya import api
|
||||
from . import lib
|
||||
from .alembic import get_alembic_ids_cache
|
||||
from .usd import is_usd_lib_supported, get_usd_ids_cache
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -74,6 +75,13 @@ def get_nodes_by_id(standin):
|
|||
# Support alembic files directly
|
||||
return get_alembic_ids_cache(path)
|
||||
|
||||
elif (
|
||||
is_usd_lib_supported and
|
||||
any(path.endswith(ext) for ext in [".usd", ".usda", ".usdc"])
|
||||
):
|
||||
# Support usd files directly
|
||||
return get_usd_ids_cache(path)
|
||||
|
||||
json_path = None
|
||||
for f in os.listdir(os.path.dirname(path)):
|
||||
if f.endswith(".json"):
|
||||
|
|
|
|||
38
openpype/hosts/maya/tools/mayalookassigner/usd.py
Normal file
38
openpype/hosts/maya/tools/mayalookassigner/usd.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
from pxr import Usd
|
||||
is_usd_lib_supported = True
|
||||
except ImportError:
|
||||
is_usd_lib_supported = False
|
||||
|
||||
|
||||
def get_usd_ids_cache(path):
|
||||
# type: (str) -> dict
|
||||
"""Build a id to node mapping in a USD file.
|
||||
|
||||
Nodes without IDs are ignored.
|
||||
|
||||
Returns:
|
||||
dict: Mapping of id to nodes in the USD file.
|
||||
|
||||
"""
|
||||
if not is_usd_lib_supported:
|
||||
raise RuntimeError("No pxr.Usd python library available.")
|
||||
|
||||
stage = Usd.Stage.Open(path)
|
||||
ids = {}
|
||||
for prim in stage.Traverse():
|
||||
attr = prim.GetAttribute("userProperties:cbId")
|
||||
if not attr.IsValid():
|
||||
continue
|
||||
value = attr.Get()
|
||||
if not value:
|
||||
continue
|
||||
path = str(prim.GetPath())
|
||||
ids[path] = value
|
||||
|
||||
cache = defaultdict(list)
|
||||
for path, value in ids.items():
|
||||
cache[value].append(path)
|
||||
return dict(cache)
|
||||
|
|
@ -6,7 +6,10 @@ from PIL import Image
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.pipeline.publish import KnownPublishError
|
||||
from openpype.pipeline.publish import (
|
||||
KnownPublishError,
|
||||
get_publish_instance_families,
|
||||
)
|
||||
from openpype.hosts.tvpaint.api.lib import (
|
||||
execute_george,
|
||||
execute_george_through_file,
|
||||
|
|
@ -140,8 +143,9 @@ class ExtractSequence(pyblish.api.Extractor):
|
|||
)
|
||||
|
||||
# Fill tags and new families from project settings
|
||||
instance_families = get_publish_instance_families(instance)
|
||||
tags = []
|
||||
if "review" in instance.data["families"]:
|
||||
if "review" in instance_families:
|
||||
tags.append("review")
|
||||
|
||||
# Sequence of one frame
|
||||
|
|
|
|||
|
|
@ -59,6 +59,14 @@ IGNORED_DEFAULT_FILENAMES = (
|
|||
"example_addons",
|
||||
"default_modules",
|
||||
)
|
||||
# Modules that won't be loaded in AYON mode from "./openpype/modules"
|
||||
# - the same modules are ignored in "./server_addon/create_ayon_addons.py"
|
||||
IGNORED_FILENAMES_IN_AYON = {
|
||||
"ftrack",
|
||||
"shotgrid",
|
||||
"sync_server",
|
||||
"slack",
|
||||
}
|
||||
|
||||
|
||||
# Inherit from `object` for Python 2 hosts
|
||||
|
|
@ -392,9 +400,9 @@ def _load_ayon_addons(openpype_modules, modules_key, log):
|
|||
folder_name = "{}_{}".format(addon_name, addon_version)
|
||||
addon_dir = os.path.join(addons_dir, folder_name)
|
||||
if not os.path.exists(addon_dir):
|
||||
log.warning((
|
||||
"Directory for addon {} {} does not exists. Path \"{}\""
|
||||
).format(addon_name, addon_version, addon_dir))
|
||||
log.debug((
|
||||
"No localized client code found for addon {} {}."
|
||||
).format(addon_name, addon_version))
|
||||
continue
|
||||
|
||||
sys.path.insert(0, addon_dir)
|
||||
|
|
@ -483,6 +491,10 @@ def _load_modules():
|
|||
|
||||
is_in_current_dir = dirpath == current_dir
|
||||
is_in_host_dir = dirpath == hosts_dir
|
||||
ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES)
|
||||
if AYON_SERVER_ENABLED:
|
||||
ignored_current_dir_filenames |= IGNORED_FILENAMES_IN_AYON
|
||||
|
||||
for filename in os.listdir(dirpath):
|
||||
# Ignore filenames
|
||||
if filename in IGNORED_FILENAMES:
|
||||
|
|
@ -490,7 +502,7 @@ def _load_modules():
|
|||
|
||||
if (
|
||||
is_in_current_dir
|
||||
and filename in IGNORED_DEFAULT_FILENAMES
|
||||
and filename in ignored_current_dir_filenames
|
||||
):
|
||||
continue
|
||||
|
||||
|
|
|
|||
|
|
@ -290,7 +290,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
def process_submission(self):
|
||||
|
||||
instance = self._instance
|
||||
context = instance.context
|
||||
|
||||
filepath = self.scene_path # publish if `use_publish` else workfile
|
||||
|
||||
|
|
@ -306,13 +305,11 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
|
|||
self._patch_workfile()
|
||||
|
||||
# Gather needed data ------------------------------------------------
|
||||
workspace = context.data["workspaceDir"]
|
||||
default_render_file = instance.context.data.get('project_settings')\
|
||||
.get('maya')\
|
||||
.get('RenderSettings')\
|
||||
.get('default_render_image_folder')
|
||||
filename = os.path.basename(filepath)
|
||||
dirname = os.path.join(workspace, default_render_file)
|
||||
dirname = os.path.join(
|
||||
cmds.workspace(query=True, rootDirectory=True),
|
||||
cmds.workspace(fileRuleEntry="images")
|
||||
)
|
||||
|
||||
# Fill in common data to payload ------------------------------------
|
||||
# TODO: Replace these with collected data from CollectRender
|
||||
|
|
|
|||
|
|
@ -345,6 +345,151 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin,
|
|||
self.log.debug("Skipping local instance.")
|
||||
return
|
||||
|
||||
data = instance.data.copy()
|
||||
context = instance.context
|
||||
self.context = context
|
||||
self.anatomy = instance.context.data["anatomy"]
|
||||
|
||||
asset = data.get("asset") or context.data["asset"]
|
||||
subset = data.get("subset")
|
||||
|
||||
start = instance.data.get("frameStart")
|
||||
if start is None:
|
||||
start = context.data["frameStart"]
|
||||
|
||||
end = instance.data.get("frameEnd")
|
||||
if end is None:
|
||||
end = context.data["frameEnd"]
|
||||
|
||||
handle_start = instance.data.get("handleStart")
|
||||
if handle_start is None:
|
||||
handle_start = context.data["handleStart"]
|
||||
|
||||
handle_end = instance.data.get("handleEnd")
|
||||
if handle_end is None:
|
||||
handle_end = context.data["handleEnd"]
|
||||
|
||||
fps = instance.data.get("fps")
|
||||
if fps is None:
|
||||
fps = context.data["fps"]
|
||||
|
||||
if data.get("extendFrames", False):
|
||||
start, end = self._extend_frames(
|
||||
asset,
|
||||
subset,
|
||||
start,
|
||||
end,
|
||||
data["overrideExistingFrame"])
|
||||
|
||||
try:
|
||||
source = data["source"]
|
||||
except KeyError:
|
||||
source = context.data["currentFile"]
|
||||
|
||||
success, rootless_path = (
|
||||
self.anatomy.find_root_template_from_path(source)
|
||||
)
|
||||
if success:
|
||||
source = rootless_path
|
||||
|
||||
else:
|
||||
# `rootless_path` is not set to `source` if none of roots match
|
||||
self.log.warning((
|
||||
"Could not find root path for remapping \"{}\"."
|
||||
" This may cause issues."
|
||||
).format(source))
|
||||
|
||||
family = "render"
|
||||
if ("prerender" in instance.data["families"] or
|
||||
"prerender.farm" in instance.data["families"]):
|
||||
family = "prerender"
|
||||
families = [family]
|
||||
|
||||
# pass review to families if marked as review
|
||||
do_not_add_review = False
|
||||
if data.get("review"):
|
||||
families.append("review")
|
||||
elif data.get("review") is False:
|
||||
self.log.debug("Instance has review explicitly disabled.")
|
||||
do_not_add_review = True
|
||||
|
||||
instance_skeleton_data = {
|
||||
"family": family,
|
||||
"subset": subset,
|
||||
"families": families,
|
||||
"asset": asset,
|
||||
"frameStart": start,
|
||||
"frameEnd": end,
|
||||
"handleStart": handle_start,
|
||||
"handleEnd": handle_end,
|
||||
"frameStartHandle": start - handle_start,
|
||||
"frameEndHandle": end + handle_end,
|
||||
"comment": instance.data["comment"],
|
||||
"fps": fps,
|
||||
"source": source,
|
||||
"extendFrames": data.get("extendFrames"),
|
||||
"overrideExistingFrame": data.get("overrideExistingFrame"),
|
||||
"pixelAspect": data.get("pixelAspect", 1),
|
||||
"resolutionWidth": data.get("resolutionWidth", 1920),
|
||||
"resolutionHeight": data.get("resolutionHeight", 1080),
|
||||
"multipartExr": data.get("multipartExr", False),
|
||||
"jobBatchName": data.get("jobBatchName", ""),
|
||||
"useSequenceForReview": data.get("useSequenceForReview", True),
|
||||
# map inputVersions `ObjectId` -> `str` so json supports it
|
||||
"inputVersions": list(map(str, data.get("inputVersions", []))),
|
||||
"colorspace": instance.data.get("colorspace"),
|
||||
"stagingDir_persistent": instance.data.get(
|
||||
"stagingDir_persistent", False
|
||||
)
|
||||
}
|
||||
|
||||
# skip locking version if we are creating v01
|
||||
instance_version = instance.data.get("version") # take this if exists
|
||||
if instance_version != 1:
|
||||
instance_skeleton_data["version"] = instance_version
|
||||
|
||||
# transfer specific families from original instance to new render
|
||||
for item in self.families_transfer:
|
||||
if item in instance.data.get("families", []):
|
||||
instance_skeleton_data["families"] += [item]
|
||||
|
||||
# transfer specific properties from original instance based on
|
||||
# mapping dictionary `instance_transfer`
|
||||
for key, values in self.instance_transfer.items():
|
||||
if key in instance.data.get("families", []):
|
||||
for v in values:
|
||||
instance_skeleton_data[v] = instance.data.get(v)
|
||||
|
||||
# look into instance data if representations are not having any
|
||||
# which are having tag `publish_on_farm` and include them
|
||||
for repre in instance.data.get("representations", []):
|
||||
staging_dir = repre.get("stagingDir")
|
||||
if staging_dir:
|
||||
success, rootless_staging_dir = (
|
||||
self.anatomy.find_root_template_from_path(
|
||||
staging_dir
|
||||
)
|
||||
)
|
||||
if success:
|
||||
repre["stagingDir"] = rootless_staging_dir
|
||||
else:
|
||||
self.log.warning((
|
||||
"Could not find root path for remapping \"{}\"."
|
||||
" This may cause issues on farm."
|
||||
).format(staging_dir))
|
||||
repre["stagingDir"] = staging_dir
|
||||
|
||||
if "publish_on_farm" in repre.get("tags"):
|
||||
# create representations attribute of not there
|
||||
if "representations" not in instance_skeleton_data.keys():
|
||||
instance_skeleton_data["representations"] = []
|
||||
|
||||
instance_skeleton_data["representations"].append(repre)
|
||||
|
||||
instances = None
|
||||
assert data.get("expectedFiles"), ("Submission from old Pype version"
|
||||
" - missing expectedFiles")
|
||||
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
|
||||
instance_skeleton_data = create_skeleton_instance(
|
||||
|
|
|
|||
|
|
@ -385,6 +385,12 @@ def inject_openpype_environment(deadlinePlugin):
|
|||
for key, value in contents.items():
|
||||
deadlinePlugin.SetProcessEnvironmentVariable(key, value)
|
||||
|
||||
if "PATH" in contents:
|
||||
# Set os.environ[PATH] so studio settings' path entries
|
||||
# can be used to define search path for executables.
|
||||
print(f">>> Setting 'PATH' Environment to: {contents['PATH']}")
|
||||
os.environ["PATH"] = contents["PATH"]
|
||||
|
||||
script_url = job.GetJobPluginInfoKeyValue("ScriptFilename")
|
||||
if script_url:
|
||||
script_url = script_url.format(**contents).replace("\\", "/")
|
||||
|
|
@ -509,6 +515,12 @@ def inject_ayon_environment(deadlinePlugin):
|
|||
for key, value in contents.items():
|
||||
deadlinePlugin.SetProcessEnvironmentVariable(key, value)
|
||||
|
||||
if "PATH" in contents:
|
||||
# Set os.environ[PATH] so studio settings' path entries
|
||||
# can be used to define search path for executables.
|
||||
print(f">>> Setting 'PATH' Environment to: {contents['PATH']}")
|
||||
os.environ["PATH"] = contents["PATH"]
|
||||
|
||||
script_url = job.GetJobPluginInfoKeyValue("ScriptFilename")
|
||||
if script_url:
|
||||
script_url = script_url.format(**contents).replace("\\", "/")
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from .lib import (
|
|||
apply_plugin_settings_automatically,
|
||||
get_plugin_settings,
|
||||
get_publish_instance_label,
|
||||
get_publish_instance_families,
|
||||
)
|
||||
|
||||
from .abstract_expected_files import ExpectedFiles
|
||||
|
|
@ -87,6 +88,7 @@ __all__ = (
|
|||
"apply_plugin_settings_automatically",
|
||||
"get_plugin_settings",
|
||||
"get_publish_instance_label",
|
||||
"get_publish_instance_families",
|
||||
|
||||
"ExpectedFiles",
|
||||
|
||||
|
|
|
|||
|
|
@ -1002,3 +1002,27 @@ def get_publish_instance_label(instance):
|
|||
or instance.data.get("name")
|
||||
or str(instance)
|
||||
)
|
||||
|
||||
|
||||
def get_publish_instance_families(instance):
|
||||
"""Get all families of the instance.
|
||||
|
||||
Look for families under 'family' and 'families' keys in instance data.
|
||||
Value of 'family' is used as first family and then all other families
|
||||
in random order.
|
||||
|
||||
Args:
|
||||
pyblish.api.Instance: Instance to get families from.
|
||||
|
||||
Returns:
|
||||
list[str]: List of families.
|
||||
"""
|
||||
|
||||
family = instance.data.get("family")
|
||||
families = set(instance.data.get("families") or [])
|
||||
output = []
|
||||
if family:
|
||||
output.append(family)
|
||||
families.discard(family)
|
||||
output.extend(families)
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -103,13 +103,16 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
|
||||
# stash render job id for later validation
|
||||
instance.data["render_job_id"] = data.get("job").get("_id")
|
||||
|
||||
staging_dir_persistent = instance.data.get(
|
||||
"stagingDir_persistent", False
|
||||
)
|
||||
representations = []
|
||||
for repre_data in instance_data.get("representations") or []:
|
||||
self._fill_staging_dir(repre_data, anatomy)
|
||||
representations.append(repre_data)
|
||||
|
||||
add_repre_files_for_cleanup(instance, repre_data)
|
||||
if not staging_dir_persistent:
|
||||
add_repre_files_for_cleanup(instance, repre_data)
|
||||
|
||||
instance.data["representations"] = representations
|
||||
|
||||
|
|
@ -124,6 +127,8 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
self.log.debug(
|
||||
f"Adding audio to instance: {instance.data['audio']}")
|
||||
|
||||
return staging_dir_persistent
|
||||
|
||||
def process(self, context):
|
||||
self._context = context
|
||||
|
||||
|
|
@ -160,9 +165,12 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
legacy_io.Session.update(session_data)
|
||||
os.environ.update(session_data)
|
||||
session_is_set = True
|
||||
self._process_path(data, anatomy)
|
||||
context.data["cleanupFullPaths"].append(path)
|
||||
context.data["cleanupEmptyDirs"].append(os.path.dirname(path))
|
||||
staging_dir_persistent = self._process_path(data, anatomy)
|
||||
if not staging_dir_persistent:
|
||||
context.data["cleanupFullPaths"].append(path)
|
||||
context.data["cleanupEmptyDirs"].append(
|
||||
os.path.dirname(path)
|
||||
)
|
||||
except Exception as e:
|
||||
self.log.error(e, exc_info=True)
|
||||
raise Exception("Error") from e
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import collections
|
||||
|
||||
import pyblish.api
|
||||
from ayon_api import create_link, make_sure_link_type_exists
|
||||
from ayon_api import (
|
||||
create_link,
|
||||
make_sure_link_type_exists,
|
||||
get_versions_links,
|
||||
)
|
||||
|
||||
from openpype import AYON_SERVER_ENABLED
|
||||
|
||||
|
|
@ -124,6 +128,33 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
|
|||
version_entity["_id"],
|
||||
)
|
||||
|
||||
def _get_existing_links(self, project_name, link_type, entity_ids):
|
||||
"""Find all existing links for given version ids.
|
||||
|
||||
Args:
|
||||
project_name (str): Name of project.
|
||||
link_type (str): Type of link.
|
||||
entity_ids (set[str]): Set of version ids.
|
||||
|
||||
Returns:
|
||||
dict[str, set[str]]: Existing links by version id.
|
||||
"""
|
||||
|
||||
output = collections.defaultdict(set)
|
||||
if not entity_ids:
|
||||
return output
|
||||
|
||||
existing_in_links = get_versions_links(
|
||||
project_name, entity_ids, [link_type], "output"
|
||||
)
|
||||
|
||||
for entity_id, links in existing_in_links.items():
|
||||
if not links:
|
||||
continue
|
||||
for link in links:
|
||||
output[entity_id].add(link["entityId"])
|
||||
return output
|
||||
|
||||
def create_links_on_server(self, context, new_links):
|
||||
"""Create new links on server.
|
||||
|
||||
|
|
@ -144,16 +175,32 @@ class IntegrateInputLinksAYON(pyblish.api.ContextPlugin):
|
|||
|
||||
# Create link themselves
|
||||
for link_type, items in new_links.items():
|
||||
mapping = collections.defaultdict(set)
|
||||
# Make sure there are no duplicates of src > dst ids
|
||||
for item in items:
|
||||
input_id, output_id = item
|
||||
create_link(
|
||||
project_name,
|
||||
link_type,
|
||||
input_id,
|
||||
"version",
|
||||
output_id,
|
||||
"version"
|
||||
)
|
||||
_input_id, _output_id = item
|
||||
mapping[_input_id].add(_output_id)
|
||||
|
||||
existing_links_by_in_id = self._get_existing_links(
|
||||
project_name, link_type, set(mapping.keys())
|
||||
)
|
||||
|
||||
for input_id, output_ids in mapping.items():
|
||||
existing_links = existing_links_by_in_id[input_id]
|
||||
for output_id in output_ids:
|
||||
# Skip creation of link if already exists
|
||||
# NOTE: AYON server does not support
|
||||
# to have same links
|
||||
if output_id in existing_links:
|
||||
continue
|
||||
create_link(
|
||||
project_name,
|
||||
link_type,
|
||||
input_id,
|
||||
"version",
|
||||
output_id,
|
||||
"version"
|
||||
)
|
||||
|
||||
|
||||
if not AYON_SERVER_ENABLED:
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
{
|
||||
"type": "text",
|
||||
"key": "default_render_image_folder",
|
||||
"label": "Default render image folder"
|
||||
"label": "Default render image folder. This setting can be\noverwritten by custom staging directory profile;\n\"project_settings/global/tools/publish\n/custom_staging_dir_profiles\"."
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.16.7-nightly.2"
|
||||
__version__ = "3.17.1-nightly.1"
|
||||
|
|
|
|||
204
poetry.lock
generated
204
poetry.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.16.6" # OpenPype
|
||||
version = "3.17.0" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
@ -36,7 +36,7 @@ appdirs = { git = "https://github.com/ActiveState/appdirs.git", branch = "master
|
|||
blessed = "^1.17" # openpype terminal formatting
|
||||
coolname = "*"
|
||||
clique = "1.6.*"
|
||||
Click = "^8"
|
||||
Click = "7.1.2"
|
||||
dnspython = "^2.1.0"
|
||||
ftrack-python-api = "^2.3.3"
|
||||
arrow = "^0.17"
|
||||
|
|
@ -56,6 +56,7 @@ QtPy = "^2.3.0"
|
|||
qtawesome = "0.7.3"
|
||||
speedcopy = "^2.1"
|
||||
six = "^1.15"
|
||||
urllib3 = "1.26.16"
|
||||
semver = "^2.13.0" # for version resolution
|
||||
wsrpc_aiohttp = "^3.1.1" # websocket server
|
||||
pywin32 = { version = "301", markers = "sys_platform == 'win32'" }
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:application-1.0",
|
||||
"description": "An application definition.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"label",
|
||||
"application_dir",
|
||||
"executable"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"description": "Nice name of application.",
|
||||
"type": "string"
|
||||
},
|
||||
"application_dir": {
|
||||
"description": "Name of directory used for application resources.",
|
||||
"type": "string"
|
||||
},
|
||||
"executable": {
|
||||
"description": "Name of callable executable, this is called to launch the application",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description of application.",
|
||||
"type": "string"
|
||||
},
|
||||
"environment": {
|
||||
"description": "Key/value pairs for environment variables related to this application. Supports lists for paths, such as PYTHONPATH.",
|
||||
"type": "object",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{"type": "string"},
|
||||
{"type": "array", "items": {"type": "string"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"default_dirs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"copy": {
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "null"}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:asset-1.0",
|
||||
"description": "A unit of data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"name",
|
||||
"subsets"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string"
|
||||
},
|
||||
"subsets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "subset.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:asset-2.0",
|
||||
"description": "A unit of data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"name",
|
||||
"silo",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:asset-2.0"],
|
||||
"example": "openpype:asset-2.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["asset"],
|
||||
"example": "asset"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"silo": {
|
||||
"description": "Group or container of asset",
|
||||
"type": "string",
|
||||
"example": "assets"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {"key": "value"}
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:asset-3.0",
|
||||
"description": "A unit of data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"name",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:asset-3.0"],
|
||||
"example": "openpype:asset-3.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["asset"],
|
||||
"example": "asset"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"silo": {
|
||||
"description": "Group or container of asset",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "assets"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {"key": "value"}
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:config-1.0",
|
||||
"description": "A project configuration.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"tasks",
|
||||
"apps"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"group": {"type": "string"},
|
||||
"label": {"type": "string"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"group": {"type": "string"},
|
||||
"label": {"type": "string"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"families": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"label": {"type": "string"},
|
||||
"hideFilter": {"type": "boolean"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"color": {"type": "string"},
|
||||
"order": {"type": ["integer", "number"]}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"copy": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:config-1.1",
|
||||
"description": "A project configuration.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"tasks",
|
||||
"apps"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"template": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"patternProperties": {
|
||||
"^.*$": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"type": "object",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"group": {"type": "string"},
|
||||
"label": {"type": "string"}
|
||||
},
|
||||
"required": [
|
||||
"short_name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"group": {"type": "string"},
|
||||
"label": {"type": "string"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"families": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"label": {"type": "string"},
|
||||
"hideFilter": {"type": "boolean"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"color": {"type": "string"},
|
||||
"order": {"type": ["integer", "number"]}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"copy": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:config-2.0",
|
||||
"description": "A project configuration.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"tasks",
|
||||
"apps"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"templates": {
|
||||
"type": "object"
|
||||
},
|
||||
"roots": {
|
||||
"type": "object"
|
||||
},
|
||||
"imageio": {
|
||||
"type": "object"
|
||||
},
|
||||
"tasks": {
|
||||
"type": "object",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"group": {"type": "string"},
|
||||
"label": {"type": "string"}
|
||||
},
|
||||
"required": [
|
||||
"short_name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"apps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"group": {"type": "string"},
|
||||
"label": {"type": "string"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"families": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"label": {"type": "string"},
|
||||
"hideFilter": {"type": "boolean"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"icon": {"type": "string"},
|
||||
"color": {"type": "string"},
|
||||
"order": {"type": ["integer", "number"]}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
},
|
||||
"copy": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:container-1.0",
|
||||
"description": "A loaded asset",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"id",
|
||||
"objectName",
|
||||
"name",
|
||||
"author",
|
||||
"loader",
|
||||
"families",
|
||||
"time",
|
||||
"subset",
|
||||
"asset",
|
||||
"representation",
|
||||
"version",
|
||||
"silo",
|
||||
"path",
|
||||
"source"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Identifier for finding object in host",
|
||||
"type": "string",
|
||||
"enum": ["pyblish.mindbender.container"],
|
||||
"example": "pyblish.mindbender.container"
|
||||
},
|
||||
"objectName": {
|
||||
"description": "Name of internal object, such as the objectSet in Maya.",
|
||||
"type": "string",
|
||||
"example": "Bruce_:rigDefault_CON"
|
||||
},
|
||||
"name": {
|
||||
"description": "Full name of application object",
|
||||
"type": "string",
|
||||
"example": "modelDefault"
|
||||
},
|
||||
"author": {
|
||||
"description": "Name of the author of the published version",
|
||||
"type": "string",
|
||||
"example": "Marcus Ottosson"
|
||||
},
|
||||
"loader": {
|
||||
"description": "Name of loader plug-in used to produce this container",
|
||||
"type": "string",
|
||||
"example": "ModelLoader"
|
||||
},
|
||||
"families": {
|
||||
"description": "Families associated with the this subset",
|
||||
"type": "string",
|
||||
"example": "mindbender.model"
|
||||
},
|
||||
"time": {
|
||||
"description": "File-system safe, formatted time",
|
||||
"type": "string",
|
||||
"example": "20170329T131545Z"
|
||||
},
|
||||
"subset": {
|
||||
"description": "Name of source subset",
|
||||
"type": "string",
|
||||
"example": "modelDefault"
|
||||
},
|
||||
"asset": {
|
||||
"description": "Name of source asset",
|
||||
"type": "string" ,
|
||||
"example": "Bruce"
|
||||
},
|
||||
"representation": {
|
||||
"description": "Name of source representation",
|
||||
"type": "string" ,
|
||||
"example": ".ma"
|
||||
},
|
||||
"version": {
|
||||
"description": "Version number",
|
||||
"type": "number",
|
||||
"example": 12
|
||||
},
|
||||
"silo": {
|
||||
"description": "Silo of parent asset",
|
||||
"type": "string",
|
||||
"example": "assets"
|
||||
},
|
||||
"path": {
|
||||
"description": "Absolute path on disk",
|
||||
"type": "string",
|
||||
"example": "{root}/assets/Bruce/publish/rigDefault/v002"
|
||||
},
|
||||
"source": {
|
||||
"description": "Absolute path to file from which this version was published",
|
||||
"type": "string",
|
||||
"example": "{root}/assets/Bruce/work/rigging/maya/scenes/rig_v001.ma"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:container-2.0",
|
||||
"description": "A loaded asset",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"id",
|
||||
"objectName",
|
||||
"name",
|
||||
"namespace",
|
||||
"loader",
|
||||
"representation"
|
||||
],
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:container-2.0"],
|
||||
"example": "openpype:container-2.0"
|
||||
},
|
||||
"id": {
|
||||
"description": "Identifier for finding object in host",
|
||||
"type": "string",
|
||||
"enum": ["pyblish.avalon.container"],
|
||||
"example": "pyblish.avalon.container"
|
||||
},
|
||||
"objectName": {
|
||||
"description": "Name of internal object, such as the objectSet in Maya.",
|
||||
"type": "string",
|
||||
"example": "Bruce_:rigDefault_CON"
|
||||
},
|
||||
"loader": {
|
||||
"description": "Name of loader plug-in used to produce this container",
|
||||
"type": "string",
|
||||
"example": "ModelLoader"
|
||||
},
|
||||
"name": {
|
||||
"description": "Internal object name of container in application",
|
||||
"type": "string",
|
||||
"example": "modelDefault_01"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "Internal namespace of container in application",
|
||||
"type": "string",
|
||||
"example": "Bruce_"
|
||||
},
|
||||
"representation": {
|
||||
"description": "Unique id of representation in database",
|
||||
"type": "string",
|
||||
"example": "59523f355f8c1b5f6c5e8348"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:hero_version-1.0",
|
||||
"description": "Hero version of asset",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"version_id",
|
||||
"schema",
|
||||
"type",
|
||||
"parent"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"_id": {
|
||||
"description": "Document's id (database will create it's if not entered)",
|
||||
"example": "ObjectId(592c33475f8c1b064c4d1696)"
|
||||
},
|
||||
"version_id": {
|
||||
"description": "The version ID from which it was created",
|
||||
"example": "ObjectId(592c33475f8c1b064c4d1695)"
|
||||
},
|
||||
"schema": {
|
||||
"description": "The schema associated with this document",
|
||||
"type": "string",
|
||||
"enum": ["openpype:hero_version-1.0"],
|
||||
"example": "openpype:hero_version-1.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["hero_version"],
|
||||
"example": "hero_version"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "ObjectId(592c33475f8c1b064c4d1697)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:config-1.0",
|
||||
"description": "A project configuration.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:config-1.1",
|
||||
"description": "A project configuration.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:project-2.0",
|
||||
"description": "A unit of data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"name",
|
||||
"data",
|
||||
"config"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:project-2.0"],
|
||||
"example": "openpype:project-2.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["project"],
|
||||
"example": "project"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "hulk"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {
|
||||
"fps": 24,
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "Document metadata",
|
||||
"example": {
|
||||
"schema": "openpype:config-1.0",
|
||||
"apps": [
|
||||
{
|
||||
"name": "maya2016",
|
||||
"label": "Autodesk Maya 2016"
|
||||
},
|
||||
{
|
||||
"name": "nuke10",
|
||||
"label": "The Foundry Nuke 10.0"
|
||||
}
|
||||
],
|
||||
"tasks": [
|
||||
{"name": "model"},
|
||||
{"name": "render"},
|
||||
{"name": "animate"},
|
||||
{"name": "rig"},
|
||||
{"name": "lookdev"},
|
||||
{"name": "layout"}
|
||||
],
|
||||
"template": {
|
||||
"work":
|
||||
"{root}/{project}/{silo}/{asset}/work/{task}/{app}",
|
||||
"publish":
|
||||
"{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}"
|
||||
}
|
||||
},
|
||||
"$ref": "config-1.0.json"
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:project-2.1",
|
||||
"description": "A unit of data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"name",
|
||||
"data",
|
||||
"config"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:project-2.1"],
|
||||
"example": "openpype:project-2.1"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["project"],
|
||||
"example": "project"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "hulk"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {
|
||||
"fps": 24,
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "Document metadata",
|
||||
"example": {
|
||||
"schema": "openpype:config-1.1",
|
||||
"apps": [
|
||||
{
|
||||
"name": "maya2016",
|
||||
"label": "Autodesk Maya 2016"
|
||||
},
|
||||
{
|
||||
"name": "nuke10",
|
||||
"label": "The Foundry Nuke 10.0"
|
||||
}
|
||||
],
|
||||
"tasks": {
|
||||
"Model": {"short_name": "mdl"},
|
||||
"Render": {"short_name": "rnd"},
|
||||
"Animate": {"short_name": "anim"},
|
||||
"Rig": {"short_name": "rig"},
|
||||
"Lookdev": {"short_name": "look"},
|
||||
"Layout": {"short_name": "lay"}
|
||||
},
|
||||
"template": {
|
||||
"work":
|
||||
"{root}/{project}/{silo}/{asset}/work/{task}/{app}",
|
||||
"publish":
|
||||
"{root}/{project}/{silo}/{asset}/publish/{subset}/v{version:0>3}/{subset}.{representation}"
|
||||
}
|
||||
},
|
||||
"$ref": "config-1.1.json"
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:project-3.0",
|
||||
"description": "A unit of data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"name",
|
||||
"data",
|
||||
"config"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:project-3.0"],
|
||||
"example": "openpype:project-3.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["project"],
|
||||
"example": "project"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "hulk"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {
|
||||
"fps": 24,
|
||||
"width": 1920,
|
||||
"height": 1080
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"description": "Document metadata",
|
||||
"$ref": "config-2.0.json"
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:representation-1.0",
|
||||
"description": "The inverse of an instance",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"format",
|
||||
"path"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {"type": "string"},
|
||||
"format": {
|
||||
"description": "File extension, including '.'",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Unformatted path to version.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:representation-2.0",
|
||||
"description": "The inverse of an instance",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"parent",
|
||||
"name",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:representation-2.0"],
|
||||
"example": "openpype:representation-2.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["representation"],
|
||||
"example": "representation"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of representation",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "abc"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {
|
||||
"label": "Alembic"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"description": "Other representation that this representation depends on",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"example": [
|
||||
"592d547a5f8c1b388093c145"
|
||||
]
|
||||
},
|
||||
"context": {
|
||||
"description": "Summary of the context to which this representation belong.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {"type": "object"},
|
||||
"asset": {"type": "string"},
|
||||
"silo": {"type": ["string", "null"]},
|
||||
"subset": {"type": "string"},
|
||||
"version": {"type": "number"},
|
||||
"representation": {"type": "string"}
|
||||
},
|
||||
"example": {
|
||||
"project": "hulk",
|
||||
"asset": "Bruce",
|
||||
"silo": "assets",
|
||||
"subset": "rigDefault",
|
||||
"version": 12,
|
||||
"representation": "ma"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:session-1.0",
|
||||
"description": "The Avalon environment",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"AVALON_PROJECTS",
|
||||
"AVALON_PROJECT",
|
||||
"AVALON_ASSET",
|
||||
"AVALON_SILO",
|
||||
"AVALON_CONFIG"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"AVALON_PROJECTS": {
|
||||
"description": "Absolute path to root of project directories",
|
||||
"type": "string",
|
||||
"example": "/nas/projects"
|
||||
},
|
||||
"AVALON_PROJECT": {
|
||||
"description": "Name of project",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Hulk"
|
||||
},
|
||||
"AVALON_ASSET": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"AVALON_SILO": {
|
||||
"description": "Name of asset group or container",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "assets"
|
||||
},
|
||||
"AVALON_TASK": {
|
||||
"description": "Name of task",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "modeling"
|
||||
},
|
||||
"AVALON_CONFIG": {
|
||||
"description": "Name of Avalon configuration",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "polly"
|
||||
},
|
||||
"AVALON_APP": {
|
||||
"description": "Name of application",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "maya2016"
|
||||
},
|
||||
"AVALON_MONGO": {
|
||||
"description": "Address to the asset database",
|
||||
"type": "string",
|
||||
"pattern": "^mongodb://[\\w/@:.]*$",
|
||||
"example": "mongodb://localhost:27017",
|
||||
"default": "mongodb://localhost:27017"
|
||||
},
|
||||
"AVALON_DB": {
|
||||
"description": "Name of database",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "avalon",
|
||||
"default": "avalon"
|
||||
},
|
||||
"AVALON_LABEL": {
|
||||
"description": "Nice name of Avalon, used in e.g. graphical user interfaces",
|
||||
"type": "string",
|
||||
"example": "Mindbender",
|
||||
"default": "Avalon"
|
||||
},
|
||||
"AVALON_SENTRY": {
|
||||
"description": "Address to Sentry",
|
||||
"type": "string",
|
||||
"pattern": "^http[\\w/@:.]*$",
|
||||
"example": "https://5b872b280de742919b115bdc8da076a5:8d278266fe764361b8fa6024af004a9c@logs.mindbender.com/2",
|
||||
"default": null
|
||||
},
|
||||
"AVALON_DEADLINE": {
|
||||
"description": "Address to Deadline",
|
||||
"type": "string",
|
||||
"pattern": "^http[\\w/@:.]*$",
|
||||
"example": "http://192.168.99.101",
|
||||
"default": null
|
||||
},
|
||||
"AVALON_TIMEOUT": {
|
||||
"description": "Wherever there is a need for a timeout, this is the default value.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]*$",
|
||||
"default": "1000",
|
||||
"example": "1000"
|
||||
},
|
||||
"AVALON_UPLOAD": {
|
||||
"description": "Boolean of whether to upload published material to central asset repository",
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"example": "True"
|
||||
},
|
||||
"AVALON_USERNAME": {
|
||||
"description": "Generic username",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"default": "avalon",
|
||||
"example": "myself"
|
||||
},
|
||||
"AVALON_PASSWORD": {
|
||||
"description": "Generic password",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"default": "secret",
|
||||
"example": "abc123"
|
||||
},
|
||||
"AVALON_INSTANCE_ID": {
|
||||
"description": "Unique identifier for instances in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.instance",
|
||||
"example": "avalon.instance"
|
||||
},
|
||||
"AVALON_CONTAINER_ID": {
|
||||
"description": "Unique identifier for a loaded representation in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.container",
|
||||
"example": "avalon.container"
|
||||
},
|
||||
"AVALON_DEBUG": {
|
||||
"description": "Enable debugging mode. Some applications may use this for e.g. extended verbosity or mock plug-ins.",
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"example": "True"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:session-2.0",
|
||||
"description": "The Avalon environment",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"AVALON_PROJECT",
|
||||
"AVALON_ASSET",
|
||||
"AVALON_CONFIG"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"AVALON_PROJECTS": {
|
||||
"description": "Absolute path to root of project directories",
|
||||
"type": "string",
|
||||
"example": "/nas/projects"
|
||||
},
|
||||
"AVALON_PROJECT": {
|
||||
"description": "Name of project",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Hulk"
|
||||
},
|
||||
"AVALON_ASSET": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"AVALON_SILO": {
|
||||
"description": "Name of asset group or container",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "assets"
|
||||
},
|
||||
"AVALON_TASK": {
|
||||
"description": "Name of task",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "modeling"
|
||||
},
|
||||
"AVALON_CONFIG": {
|
||||
"description": "Name of Avalon configuration",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "polly"
|
||||
},
|
||||
"AVALON_APP": {
|
||||
"description": "Name of application",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "maya2016"
|
||||
},
|
||||
"AVALON_DB": {
|
||||
"description": "Name of database",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "avalon",
|
||||
"default": "avalon"
|
||||
},
|
||||
"AVALON_LABEL": {
|
||||
"description": "Nice name of Avalon, used in e.g. graphical user interfaces",
|
||||
"type": "string",
|
||||
"example": "Mindbender",
|
||||
"default": "Avalon"
|
||||
},
|
||||
"AVALON_SENTRY": {
|
||||
"description": "Address to Sentry",
|
||||
"type": "string",
|
||||
"pattern": "^http[\\w/@:.]*$",
|
||||
"example": "https://5b872b280de742919b115bdc8da076a5:8d278266fe764361b8fa6024af004a9c@logs.mindbender.com/2",
|
||||
"default": null
|
||||
},
|
||||
"AVALON_DEADLINE": {
|
||||
"description": "Address to Deadline",
|
||||
"type": "string",
|
||||
"pattern": "^http[\\w/@:.]*$",
|
||||
"example": "http://192.168.99.101",
|
||||
"default": null
|
||||
},
|
||||
"AVALON_TIMEOUT": {
|
||||
"description": "Wherever there is a need for a timeout, this is the default value.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]*$",
|
||||
"default": "1000",
|
||||
"example": "1000"
|
||||
},
|
||||
"AVALON_UPLOAD": {
|
||||
"description": "Boolean of whether to upload published material to central asset repository",
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"example": "True"
|
||||
},
|
||||
"AVALON_USERNAME": {
|
||||
"description": "Generic username",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"default": "avalon",
|
||||
"example": "myself"
|
||||
},
|
||||
"AVALON_PASSWORD": {
|
||||
"description": "Generic password",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"default": "secret",
|
||||
"example": "abc123"
|
||||
},
|
||||
"AVALON_INSTANCE_ID": {
|
||||
"description": "Unique identifier for instances in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.instance",
|
||||
"example": "avalon.instance"
|
||||
},
|
||||
"AVALON_CONTAINER_ID": {
|
||||
"description": "Unique identifier for a loaded representation in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.container",
|
||||
"example": "avalon.container"
|
||||
},
|
||||
"AVALON_DEBUG": {
|
||||
"description": "Enable debugging mode. Some applications may use this for e.g. extended verbosity or mock plug-ins.",
|
||||
"type": "string",
|
||||
"default": null,
|
||||
"example": "True"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:session-3.0",
|
||||
"description": "The Avalon environment",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"AVALON_PROJECT",
|
||||
"AVALON_ASSET"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"AVALON_PROJECTS": {
|
||||
"description": "Absolute path to root of project directories",
|
||||
"type": "string",
|
||||
"example": "/nas/projects"
|
||||
},
|
||||
"AVALON_PROJECT": {
|
||||
"description": "Name of project",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Hulk"
|
||||
},
|
||||
"AVALON_ASSET": {
|
||||
"description": "Name of asset",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "Bruce"
|
||||
},
|
||||
"AVALON_TASK": {
|
||||
"description": "Name of task",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "modeling"
|
||||
},
|
||||
"AVALON_APP": {
|
||||
"description": "Name of host",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "maya2016"
|
||||
},
|
||||
"AVALON_DB": {
|
||||
"description": "Name of database",
|
||||
"type": "string",
|
||||
"pattern": "^\\w*$",
|
||||
"example": "avalon",
|
||||
"default": "avalon"
|
||||
},
|
||||
"AVALON_LABEL": {
|
||||
"description": "Nice name of Avalon, used in e.g. graphical user interfaces",
|
||||
"type": "string",
|
||||
"example": "Mindbender",
|
||||
"default": "Avalon"
|
||||
},
|
||||
"AVALON_TIMEOUT": {
|
||||
"description": "Wherever there is a need for a timeout, this is the default value.",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]*$",
|
||||
"default": "1000",
|
||||
"example": "1000"
|
||||
},
|
||||
"AVALON_INSTANCE_ID": {
|
||||
"description": "Unique identifier for instances in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.instance",
|
||||
"example": "avalon.instance"
|
||||
},
|
||||
"AVALON_CONTAINER_ID": {
|
||||
"description": "Unique identifier for a loaded representation in a working file",
|
||||
"type": "string",
|
||||
"pattern": "^[\\w.]*$",
|
||||
"default": "avalon.container",
|
||||
"example": "avalon.container"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:shaders-1.0",
|
||||
"description": "Relationships between shaders and Avalon IDs",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"shader"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"shader": {
|
||||
"description": "Name of directory",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "str",
|
||||
"description": "Avalon ID and optional face indexes, e.g. 'f9520572-ac1d-11e6-b39e-3085a99791c9.f[5002:5185]'"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:subset-1.0",
|
||||
"description": "A container of instances",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"name",
|
||||
"versions"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string"
|
||||
},
|
||||
"versions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "version.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"definitions": {}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:subset-2.0",
|
||||
"description": "A container of instances",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"parent",
|
||||
"name",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "The schema associated with this document",
|
||||
"type": "string",
|
||||
"enum": ["openpype:subset-2.0"],
|
||||
"example": "openpype:subset-2.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["subset"],
|
||||
"example": "subset"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "shot01"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Document metadata",
|
||||
"example": {
|
||||
"frameStart": 1000,
|
||||
"frameEnd": 1201
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:subset-3.0",
|
||||
"description": "A container of instances",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"parent",
|
||||
"name",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "The schema associated with this document",
|
||||
"type": "string",
|
||||
"enum": ["openpype:subset-3.0"],
|
||||
"example": "openpype:subset-3.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["subset"],
|
||||
"example": "subset"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name of directory",
|
||||
"type": "string",
|
||||
"pattern": "^[a-zA-Z0-9_.]*$",
|
||||
"example": "shot01"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"required": ["families"],
|
||||
"properties": {
|
||||
"families": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "One or more families associated with this subset"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"families" : [
|
||||
"avalon.camera"
|
||||
],
|
||||
"frameStart": 1000,
|
||||
"frameEnd": 1201
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:thumbnail-1.0",
|
||||
"description": "Entity with thumbnail data",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "The schema associated with this document",
|
||||
"type": "string",
|
||||
"enum": ["openpype:thumbnail-1.0"],
|
||||
"example": "openpype:thumbnail-1.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["thumbnail"],
|
||||
"example": "thumbnail"
|
||||
},
|
||||
"data": {
|
||||
"description": "Thumbnail data",
|
||||
"type": "object",
|
||||
"example": {
|
||||
"binary_data": "Binary({byte data of image})",
|
||||
"template": "{thumbnail_root}/{project[name]}/{_id}{ext}}",
|
||||
"template_data": {
|
||||
"ext": ".jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:version-1.0",
|
||||
"description": "An individual version",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"version",
|
||||
"path",
|
||||
"time",
|
||||
"author",
|
||||
"source",
|
||||
"representations"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {"type": "string"},
|
||||
"representations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "representation.json"
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"description": "ISO formatted, file-system compatible time",
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"description": "User logged on to the machine at time of publish",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "Number of this version",
|
||||
"type": "number"
|
||||
},
|
||||
"path": {
|
||||
"description": "Unformatted path, e.g. '{root}/assets/Bruce/publish/lookdevDefault/v001",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Original file from which this version was made.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:version-2.0",
|
||||
"description": "An individual version",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"parent",
|
||||
"name",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "The schema associated with this document",
|
||||
"type": "string",
|
||||
"enum": ["openpype:version-2.0"],
|
||||
"example": "openpype:version-2.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["version"],
|
||||
"example": "version"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Number of version",
|
||||
"type": "number",
|
||||
"example": 12
|
||||
},
|
||||
"locations": {
|
||||
"description": "Where on the planet this version can be found.",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"example": ["data.avalon.com"]
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"required": ["families", "author", "source", "time"],
|
||||
"properties": {
|
||||
"time": {
|
||||
"description": "ISO formatted, file-system compatible time",
|
||||
"type": "string"
|
||||
},
|
||||
"timeFormat": {
|
||||
"description": "ISO format of time",
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"description": "User logged on to the machine at time of publish",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "Number of this version",
|
||||
"type": "number"
|
||||
},
|
||||
"path": {
|
||||
"description": "Unformatted path, e.g. '{root}/assets/Bruce/publish/lookdevDefault/v001",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Original file from which this version was made.",
|
||||
"type": "string"
|
||||
},
|
||||
"families": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "One or more families associated with this version"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"source" : "{root}/f02_prod/assets/BubbleWitch/work/modeling/marcus/maya/scenes/model_v001.ma",
|
||||
"author" : "marcus",
|
||||
"families" : [
|
||||
"avalon.model"
|
||||
],
|
||||
"time" : "20170510T090203Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:version-3.0",
|
||||
"description": "An individual version",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"parent",
|
||||
"name",
|
||||
"data"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "The schema associated with this document",
|
||||
"type": "string",
|
||||
"enum": ["openpype:version-3.0"],
|
||||
"example": "openpype:version-3.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["version"],
|
||||
"example": "version"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"name": {
|
||||
"description": "Number of version",
|
||||
"type": "number",
|
||||
"example": 12
|
||||
},
|
||||
"locations": {
|
||||
"description": "Where on the planet this version can be found.",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"example": ["data.avalon.com"]
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"required": ["author", "source", "time"],
|
||||
"properties": {
|
||||
"time": {
|
||||
"description": "ISO formatted, file-system compatible time",
|
||||
"type": "string"
|
||||
},
|
||||
"timeFormat": {
|
||||
"description": "ISO format of time",
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"description": "User logged on to the machine at time of publish",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "Number of this version",
|
||||
"type": "number"
|
||||
},
|
||||
"path": {
|
||||
"description": "Unformatted path, e.g. '{root}/assets/Bruce/publish/lookdevDefault/v001",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Original file from which this version was made.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"source" : "{root}/f02_prod/assets/BubbleWitch/work/modeling/marcus/maya/scenes/model_v001.ma",
|
||||
"author" : "marcus",
|
||||
"time" : "20170510T090203Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
|
||||
"title": "openpype:workfile-1.0",
|
||||
"description": "Workfile additional information.",
|
||||
|
||||
"type": "object",
|
||||
|
||||
"additionalProperties": true,
|
||||
|
||||
"required": [
|
||||
"schema",
|
||||
"type",
|
||||
"filename",
|
||||
"task_name",
|
||||
"parent"
|
||||
],
|
||||
|
||||
"properties": {
|
||||
"schema": {
|
||||
"description": "Schema identifier for payload",
|
||||
"type": "string",
|
||||
"enum": ["openpype:workfile-1.0"],
|
||||
"example": "openpype:workfile-1.0"
|
||||
},
|
||||
"type": {
|
||||
"description": "The type of document",
|
||||
"type": "string",
|
||||
"enum": ["workfile"],
|
||||
"example": "workfile"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Unique identifier to parent document",
|
||||
"example": "592c33475f8c1b064c4d1696"
|
||||
},
|
||||
"filename": {
|
||||
"description": "Workfile's filename",
|
||||
"type": "string",
|
||||
"example": "kuba_each_case_Alpaca_01_animation_v001.ma"
|
||||
},
|
||||
"task_name": {
|
||||
"description": "Task name",
|
||||
"type": "string",
|
||||
"example": "animation"
|
||||
},
|
||||
"data": {
|
||||
"description": "Document metadata",
|
||||
"type": "object",
|
||||
"example": {"key": "value"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,4 +28,11 @@ omit = /tests
|
|||
directory = ./coverage
|
||||
|
||||
[tool:pytest]
|
||||
norecursedirs = repos/* openpype/modules/ftrack/*
|
||||
norecursedirs = openpype/modules/ftrack/*
|
||||
|
||||
[isort]
|
||||
line_length = 79
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = True
|
||||
force_grid_wrap = 0
|
||||
combine_as_imports = True
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -124,7 +124,6 @@ bin_includes = [
|
|||
include_files = [
|
||||
"igniter",
|
||||
"openpype",
|
||||
"schema",
|
||||
"LICENSE",
|
||||
"README.md"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -16,13 +16,19 @@ OpenPype stores some of it's settings and configuration in local file system. Th
|
|||
## Categories
|
||||
|
||||
### OpenPype Mongo URL
|
||||
The **Mongo URL** is the database URL given by your Studio. More details [here](artist_getting_started.md#mongodb).
|
||||
|
||||
### General
|
||||
**OpenPype Username** : enter your username (if not provided, it uses computer session username by default). This username is used to sign your actions on **OpenPype**, for example the "author" on a publish.
|
||||
|
||||
**Admin permissions** : When enabled you do not need to enter a password (if defined in Studio Settings) to access to the **Admin** section.
|
||||
### Experimental tools
|
||||
Future version of existing tools or new ones.
|
||||
### Environments
|
||||
Local replacement of the environment data of each software and additional internal data necessary to be loaded correctly.
|
||||
|
||||
### Applications
|
||||
Local override of software executable paths for each version. More details [here](admin_settings_system.md#applications).
|
||||
|
||||
### Project Settings
|
||||
|
||||
|
||||
The **Project Settings** allows to determine the root folder. More details [here](module_site_sync.md#local-settings).
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 11 KiB |
|
|
@ -192,7 +192,7 @@ A profile may generate multiple outputs from a single input. Each output must de
|
|||
- Nuke extractor settings path: `project_settings/nuke/publish/ExtractReviewDataMov/outputs/baking/add_custom_tags`
|
||||
- Filtering by input length. Input may be video, sequence or single image. It is possible that `.mp4` should be created only when input is video or sequence and to create review `.png` when input is single frame. In some cases the output should be created even if it's single frame or multi frame input.
|
||||
|
||||
|
||||
|
||||
### Extract Burnin
|
||||
|
||||
Plugin is responsible for adding burnins into review representations.
|
||||
|
|
@ -226,13 +226,13 @@ A burnin profile may set multiple burnin outputs from one input. The burnin's na
|
|||
| **Bottom Centered** | Bottom center content. | str | "{username}" |
|
||||
| **Bottom Right** | Bottom right corner content. | str | "{frame_start}-{current_frame}-{frame_end}" |
|
||||
|
||||
Each burnin profile can be configured with additional family filtering and can
|
||||
add additional tags to the burnin representation, these can be configured under
|
||||
Each burnin profile can be configured with additional family filtering and can
|
||||
add additional tags to the burnin representation, these can be configured under
|
||||
the profile's **Additional filtering** section.
|
||||
|
||||
:::note Filename suffix
|
||||
The filename suffix is appended to filename of the source representation. For
|
||||
example, if the source representation has suffix **"h264"** and the burnin
|
||||
The filename suffix is appended to filename of the source representation. For
|
||||
example, if the source representation has suffix **"h264"** and the burnin
|
||||
suffix is **"client"** then the final suffix is **"h264_client"**.
|
||||
:::
|
||||
|
||||
|
|
@ -343,6 +343,10 @@ One of the key advantages of this feature is that it allows users to choose the
|
|||
|
||||
In some cases, these DCCs (Nuke, Houdini, Maya) automatically add a rendering path during the creation stage, which is then used in publishing. Creators and extractors of such DCCs need to use these profiles to fill paths in DCC's nodes to use this functionality.
|
||||
|
||||
:::note
|
||||
Maya's setting `project_settings/maya/RenderSettings/default_render_image_folder` is be overwritten by the custom staging dir.
|
||||
:::
|
||||
|
||||
The custom staging folder uses a path template configured in `project_anatomy/templates/others` with `transient` being a default example path that could be used. The template requires a 'folder' key for it to be usable as custom staging folder.
|
||||
|
||||
##### Known issues
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue