mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into chore/add_zbrush_as_application
This commit is contained in:
commit
aa6a9800a7
194 changed files with 3327 additions and 2794 deletions
|
|
@ -27,7 +27,7 @@ AYON addons should contain separated logic of specific kind of implementation, s
|
|||
- default interfaces are defined in `interfaces.py`
|
||||
|
||||
## IPluginPaths
|
||||
- addon wants to add directory path/s to avalon or publish plugins
|
||||
- addon wants to add directory path/s to publish, load, create or inventory plugins
|
||||
- addon must implement `get_plugin_paths` which must return dictionary with possible keys `"publish"`, `"load"`, `"create"` or `"actions"`
|
||||
- each key may contain list or string with a path to directory with plugins
|
||||
|
||||
|
|
@ -89,4 +89,4 @@ AYON addons should contain separated logic of specific kind of implementation, s
|
|||
|
||||
### TrayAddonsManager
|
||||
- inherits from `AddonsManager`
|
||||
- has specific implementation for Pype Tray tool and handle `ITrayAddon` methods
|
||||
- has specific implementation for AYON Tray and handle `ITrayAddon` methods
|
||||
|
|
|
|||
|
|
@ -741,7 +741,7 @@ class AddonsManager:
|
|||
|
||||
addon_classes = []
|
||||
for module in openpype_modules:
|
||||
# Go through globals in `pype.modules`
|
||||
# Go through globals in `ayon_core.modules`
|
||||
for name in dir(module):
|
||||
modules_item = getattr(module, name, None)
|
||||
# Filter globals that are not classes which inherit from
|
||||
|
|
|
|||
|
|
@ -67,8 +67,6 @@ class Commands:
|
|||
install_ayon_plugins,
|
||||
get_global_context,
|
||||
)
|
||||
from ayon_core.tools.utils.host_tools import show_publish
|
||||
from ayon_core.tools.utils.lib import qt_app_context
|
||||
|
||||
# Register target and host
|
||||
import pyblish.api
|
||||
|
|
@ -134,6 +132,8 @@ class Commands:
|
|||
print(plugin)
|
||||
|
||||
if gui:
|
||||
from ayon_core.tools.utils.host_tools import show_publish
|
||||
from ayon_core.tools.utils.lib import qt_app_context
|
||||
with qt_app_context():
|
||||
show_publish()
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ class ValidateFileSaved(pyblish.api.ContextPlugin,
|
|||
if not context.data["currentFile"]:
|
||||
# File has not been saved at all and has no filename
|
||||
raise PublishValidationError(
|
||||
"Current file is empty. Save the file before continuing."
|
||||
"Current workfile has not been saved yet.\n"
|
||||
"Save the workfile before continuing."
|
||||
)
|
||||
|
||||
# Do not validate workfile has unsaved changes if only instances
|
||||
|
|
|
|||
|
|
@ -0,0 +1,94 @@
|
|||
import inspect
|
||||
from typing import List
|
||||
|
||||
import bpy
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin,
|
||||
PublishValidationError,
|
||||
RepairAction
|
||||
)
|
||||
import ayon_core.hosts.blender.api.action
|
||||
|
||||
|
||||
class ValidateModelMeshUvMap1(
|
||||
pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin,
|
||||
):
|
||||
"""Validate model mesh uvs are named `map1`.
|
||||
|
||||
This is solely to get them to work nicely for the Maya pipeline.
|
||||
"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["model"]
|
||||
label = "Mesh UVs named map1"
|
||||
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction,
|
||||
RepairAction]
|
||||
optional = True
|
||||
enabled = False
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance) -> List:
|
||||
|
||||
invalid = []
|
||||
for obj in instance:
|
||||
if obj.mode != "OBJECT":
|
||||
cls.log.warning(
|
||||
f"Mesh object {obj.name} should be in 'OBJECT' mode"
|
||||
" to be properly checked."
|
||||
)
|
||||
|
||||
obj_data = obj.data
|
||||
if isinstance(obj_data, bpy.types.Mesh):
|
||||
mesh = obj_data
|
||||
|
||||
# Ignore mesh without UVs
|
||||
if not mesh.uv_layers:
|
||||
continue
|
||||
|
||||
# If mesh has map1 all is ok
|
||||
if mesh.uv_layers.get("map1"):
|
||||
continue
|
||||
|
||||
cls.log.warning(
|
||||
f"Mesh object {obj.name} should be in 'OBJECT' mode"
|
||||
" to be properly checked."
|
||||
)
|
||||
invalid.append(obj)
|
||||
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
for obj in cls.get_invalid(instance):
|
||||
mesh = obj.data
|
||||
|
||||
# Rename the first UV set to map1
|
||||
mesh.uv_layers[0].name = "map1"
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
f"Meshes found in instance without valid UV's: {invalid}",
|
||||
description=self.get_description()
|
||||
)
|
||||
|
||||
def get_description(self):
|
||||
return inspect.cleandoc(
|
||||
"""## Meshes must have map1 uv set
|
||||
|
||||
To accompany a better Maya-focused pipeline with Alembics it is
|
||||
expected that a Mesh has a `map1` UV set. Blender defaults to
|
||||
a UV set named `UVMap` and thus needs to be renamed.
|
||||
|
||||
"""
|
||||
)
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import inspect
|
||||
from typing import List
|
||||
|
||||
import mathutils
|
||||
|
|
@ -5,29 +6,26 @@ import bpy
|
|||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.hosts.blender.api import plugin, lib
|
||||
import ayon_core.hosts.blender.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
RepairAction
|
||||
)
|
||||
|
||||
|
||||
class ValidateTransformZero(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Transforms can't have any values
|
||||
|
||||
To solve this issue, try freezing the transforms. So long
|
||||
as the transforms, rotation and scale values are zero,
|
||||
you're all good.
|
||||
|
||||
"""
|
||||
"""Transforms can't have any values"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
hosts = ["blender"]
|
||||
families = ["model"]
|
||||
label = "Transform Zero"
|
||||
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction]
|
||||
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction,
|
||||
RepairAction]
|
||||
|
||||
_identity = mathutils.Matrix()
|
||||
|
||||
|
|
@ -51,5 +49,46 @@ class ValidateTransformZero(pyblish.api.InstancePlugin,
|
|||
names = ", ".join(obj.name for obj in invalid)
|
||||
raise PublishValidationError(
|
||||
"Objects found in instance which do not"
|
||||
f" have transform set to zero: {names}"
|
||||
f" have transform set to zero: {names}",
|
||||
description=self.get_description()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
||||
invalid = cls.get_invalid(instance)
|
||||
if not invalid:
|
||||
return
|
||||
|
||||
context = plugin.create_blender_context(
|
||||
active=invalid[0], selected=invalid
|
||||
)
|
||||
with lib.maintained_selection():
|
||||
with bpy.context.temp_override(**context):
|
||||
plugin.deselect_all()
|
||||
for obj in invalid:
|
||||
obj.select_set(True)
|
||||
|
||||
# TODO: Preferably this does allow custom pivot point locations
|
||||
# and if so, this should likely apply to the delta instead
|
||||
# using `bpy.ops.object.transforms_to_deltas(mode="ALL")`
|
||||
bpy.ops.object.transform_apply(location=True,
|
||||
rotation=True,
|
||||
scale=True)
|
||||
|
||||
def get_description(self):
|
||||
return inspect.cleandoc(
|
||||
"""## Transforms can't have any values.
|
||||
|
||||
The location, rotation and scale on the transform must be at
|
||||
the default values. This also goes for the delta transforms.
|
||||
|
||||
To solve this issue, try freezing the transforms:
|
||||
- `Object` > `Apply` > `All Transforms`
|
||||
|
||||
Using the Repair action directly will do the same.
|
||||
|
||||
So long as the transforms, rotation and scale values are zero,
|
||||
you're all good.
|
||||
"""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
|||
def process(self, instance):
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
anatomy_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
padding = anatomy.templates.get("frame_padding", 4)
|
||||
padding = anatomy.templates_obj.frame_padding
|
||||
product_type = "render"
|
||||
anatomy_data.update({
|
||||
"frame": f"%0{padding}d",
|
||||
|
|
@ -28,15 +28,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
|||
})
|
||||
anatomy_data["product"]["type"] = product_type
|
||||
|
||||
anatomy_filled = anatomy.format(anatomy_data)
|
||||
|
||||
# get anatomy rendering keys
|
||||
r_anatomy_key = self.anatomy_template_key_render_files
|
||||
m_anatomy_key = self.anatomy_template_key_metadata
|
||||
|
||||
# get folder and path for rendering images from celaction
|
||||
render_dir = anatomy_filled[r_anatomy_key]["folder"]
|
||||
render_path = anatomy_filled[r_anatomy_key]["path"]
|
||||
r_template_item = anatomy.get_template_item("publish", r_anatomy_key)
|
||||
render_dir = r_template_item["directory"].format_strict(anatomy_data)
|
||||
render_path = r_template_item["path"].format_strict(anatomy_data)
|
||||
self.log.debug("__ render_path: `{}`".format(render_path))
|
||||
|
||||
# create dir if it doesnt exists
|
||||
|
|
@ -51,11 +50,14 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
|||
instance.data["path"] = render_path
|
||||
|
||||
# get anatomy for published renders folder path
|
||||
if anatomy_filled.get(m_anatomy_key):
|
||||
instance.data["publishRenderMetadataFolder"] = anatomy_filled[
|
||||
m_anatomy_key]["folder"]
|
||||
self.log.info("Metadata render path: `{}`".format(
|
||||
instance.data["publishRenderMetadataFolder"]
|
||||
))
|
||||
m_template_item = anatomy.get_template_item(
|
||||
"publish", m_anatomy_key, default=None
|
||||
)
|
||||
if m_template_item is not None:
|
||||
metadata_path = m_template_item["directory"].format_strict(
|
||||
anatomy_data
|
||||
)
|
||||
instance.data["publishRenderMetadataFolder"] = metadata_path
|
||||
self.log.info("Metadata render path: `{}`".format(metadata_path))
|
||||
|
||||
self.log.info(f"Render output path set to: `{render_path}`")
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
OpenPype Autodesk Flame api
|
||||
AYON Autodesk Flame api
|
||||
"""
|
||||
from .constants import (
|
||||
COLOR_MAP,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
|
||||
"""
|
||||
OpenPype Flame api constances
|
||||
AYON Flame api constances
|
||||
"""
|
||||
# OpenPype marker workflow variables
|
||||
# AYON marker workflow variables
|
||||
MARKER_NAME = "OpenPypeData"
|
||||
MARKER_DURATION = 0
|
||||
MARKER_COLOR = "cyan"
|
||||
MARKER_PUBLISH_DEFAULT = False
|
||||
|
||||
# OpenPype color definitions
|
||||
# AYON color definitions
|
||||
COLOR_MAP = {
|
||||
"red": (1.0, 0.0, 0.0),
|
||||
"orange": (1.0, 0.5, 0.0),
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ def install():
|
|||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
register_loader_plugin_path(LOAD_PATH)
|
||||
register_creator_plugin_path(CREATE_PATH)
|
||||
log.info("OpenPype Flame plug-ins registered ...")
|
||||
log.info("AYON Flame plug-ins registered ...")
|
||||
|
||||
# register callback for switching publishable
|
||||
pyblish.register_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
||||
log.info("OpenPype Flame host installed ...")
|
||||
log.info("AYON Flame host installed ...")
|
||||
|
||||
|
||||
def uninstall():
|
||||
|
|
@ -57,7 +57,7 @@ def uninstall():
|
|||
# register callback for switching publishable
|
||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||
|
||||
log.info("OpenPype Flame host uninstalled ...")
|
||||
log.info("AYON Flame host uninstalled ...")
|
||||
|
||||
|
||||
def containerise(flame_clip_segment,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class CreatorWidget(QtWidgets.QDialog):
|
|||
| QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
self.setWindowTitle(name or "Pype Creator Input")
|
||||
self.setWindowTitle(name or "AYON Creator Input")
|
||||
self.resize(500, 700)
|
||||
|
||||
# Where inputs and labels are set
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class WireTapCom(object):
|
|||
|
||||
def get_launch_args(
|
||||
self, project_name, project_data, user_name, *args, **kwargs):
|
||||
"""Forming launch arguments for OpenPype launcher.
|
||||
"""Forming launch arguments for AYON launcher.
|
||||
|
||||
Args:
|
||||
project_name (str): name of project
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ log = Logger.get_logger(__name__)
|
|||
def _sync_utility_scripts(env=None):
|
||||
""" Synchronizing basic utlility scripts for flame.
|
||||
|
||||
To be able to run start OpenPype within Flame we have to copy
|
||||
To be able to run start AYON within Flame we have to copy
|
||||
all utility_scripts and additional FLAME_SCRIPT_DIR into
|
||||
`/opt/Autodesk/shared/python`. This will be always synchronizing those
|
||||
folders.
|
||||
|
|
@ -124,7 +124,7 @@ def setup(env=None):
|
|||
# synchronize resolve utility scripts
|
||||
_sync_utility_scripts(env)
|
||||
|
||||
log.info("Flame OpenPype wrapper has been installed")
|
||||
log.info("Flame AYON wrapper has been installed")
|
||||
|
||||
|
||||
def get_flame_version():
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class FlamePrelaunch(PreLaunchHook):
|
|||
project_data = {
|
||||
"Name": project_entity["name"],
|
||||
"Nickname": project_entity["code"],
|
||||
"Description": "Created by OpenPype",
|
||||
"Description": "Created by AYON",
|
||||
"SetupDir": project_entity["name"],
|
||||
"FrameWidth": int(width),
|
||||
"FrameHeight": int(height),
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class FlameBabyPublisherPanel(object):
|
|||
|
||||
# creating ui
|
||||
self.window.setMinimumSize(1500, 600)
|
||||
self.window.setWindowTitle('OpenPype: Baby-publisher')
|
||||
self.window.setWindowTitle('AYON: Baby-publisher')
|
||||
self.window.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
|
||||
self.window.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
||||
self.window.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def scope_sequence(selection):
|
|||
def get_media_panel_custom_ui_actions():
|
||||
return [
|
||||
{
|
||||
"name": "OpenPype: Baby-publisher",
|
||||
"name": "AYON: Baby-publisher",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Create Shots",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ from ayon_core.pipeline import (
|
|||
|
||||
|
||||
def openpype_install():
|
||||
"""Registering OpenPype in context
|
||||
"""Registering AYON in context
|
||||
"""
|
||||
install_host(opfapi)
|
||||
print("Registered host: {}".format(registered_host()))
|
||||
|
|
@ -28,7 +28,7 @@ def exeption_handler(exctype, value, _traceback):
|
|||
tb (str): traceback to show
|
||||
"""
|
||||
import traceback
|
||||
msg = "OpenPype: Python exception {} in {}".format(value, exctype)
|
||||
msg = "AYON: Python exception {} in {}".format(value, exctype)
|
||||
mbox = QtWidgets.QMessageBox()
|
||||
mbox.setText(msg)
|
||||
mbox.setDetailedText(
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from .lib import (
|
|||
comp_lock_and_undo_chunk
|
||||
)
|
||||
|
||||
from .menu import launch_openpype_menu
|
||||
from .menu import launch_ayon_menu
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
|
@ -35,5 +35,5 @@ __all__ = [
|
|||
"comp_lock_and_undo_chunk",
|
||||
|
||||
# menu
|
||||
"launch_openpype_menu",
|
||||
"launch_ayon_menu",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ self = sys.modules[__name__]
|
|||
self.menu = None
|
||||
|
||||
|
||||
class OpenPypeMenu(QtWidgets.QWidget):
|
||||
class AYONMenu(QtWidgets.QWidget):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OpenPypeMenu, self).__init__(*args, **kwargs)
|
||||
super(AYONMenu, self).__init__(*args, **kwargs)
|
||||
|
||||
self.setObjectName(f"{MENU_LABEL}Menu")
|
||||
|
||||
|
|
@ -125,7 +125,7 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
self._pulse = FusionPulse(parent=self)
|
||||
self._pulse.start()
|
||||
|
||||
# Detect Fusion events as OpenPype events
|
||||
# Detect Fusion events as AYON events
|
||||
self._event_handler = FusionEventHandler(parent=self)
|
||||
self._event_handler.start()
|
||||
|
||||
|
|
@ -174,16 +174,16 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
set_current_context_framerange()
|
||||
|
||||
|
||||
def launch_openpype_menu():
|
||||
def launch_ayon_menu():
|
||||
app = get_qt_app()
|
||||
|
||||
pype_menu = OpenPypeMenu()
|
||||
ayon_menu = AYONMenu()
|
||||
|
||||
stylesheet = load_stylesheet()
|
||||
pype_menu.setStyleSheet(stylesheet)
|
||||
ayon_menu.setStyleSheet(stylesheet)
|
||||
|
||||
pype_menu.show()
|
||||
self.menu = pype_menu
|
||||
ayon_menu.show()
|
||||
self.menu = ayon_menu
|
||||
|
||||
result = app.exec_()
|
||||
print("Shutting down..")
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class FusionHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
name = "fusion"
|
||||
|
||||
def install(self):
|
||||
"""Install fusion-specific functionality of OpenPype.
|
||||
"""Install fusion-specific functionality of AYON.
|
||||
|
||||
This is where you install menus and register families, data
|
||||
and loaders into fusion.
|
||||
|
|
@ -177,7 +177,7 @@ def on_after_open(event):
|
|||
if any_outdated_containers():
|
||||
log.warning("Scene has outdated content.")
|
||||
|
||||
# Find OpenPype menu to attach to
|
||||
# Find AYON menu to attach to
|
||||
from . import menu
|
||||
|
||||
def _on_show_scene_inventory():
|
||||
|
|
@ -326,9 +326,9 @@ class FusionEventThread(QtCore.QThread):
|
|||
|
||||
|
||||
class FusionEventHandler(QtCore.QObject):
|
||||
"""Emits OpenPype events based on Fusion events captured in a QThread.
|
||||
"""Emits AYON events based on Fusion events captured in a QThread.
|
||||
|
||||
This will emit the following OpenPype events based on Fusion actions:
|
||||
This will emit the following AYON events based on Fusion actions:
|
||||
save: Comp_Save, Comp_SaveAs
|
||||
open: Comp_Opened
|
||||
new: Comp_New
|
||||
|
|
@ -374,7 +374,7 @@ class FusionEventHandler(QtCore.QObject):
|
|||
self._event_thread.stop()
|
||||
|
||||
def _on_event(self, event):
|
||||
"""Handle Fusion events to emit OpenPype events"""
|
||||
"""Handle Fusion events to emit AYON events"""
|
||||
if not event:
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class GenericCreateSaver(Creator):
|
|||
formatting_data = deepcopy(data)
|
||||
|
||||
# get frame padding from anatomy templates
|
||||
frame_padding = self.project_anatomy.templates["frame_padding"]
|
||||
frame_padding = self.project_anatomy.templates_obj.frame_padding
|
||||
|
||||
# get output format
|
||||
ext = data["creator_attributes"]["image_format"]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
### OpenPype deploy MenuScripts
|
||||
### AYON deploy MenuScripts
|
||||
|
||||
Note that this `MenuScripts` is not an official Fusion folder.
|
||||
OpenPype only uses this folder in `{fusion}/deploy/` to trigger the OpenPype menu actions.
|
||||
AYON only uses this folder in `{fusion}/deploy/` to trigger the AYON menu actions.
|
||||
|
||||
They are used in the actions defined in `.fu` files in `{fusion}/deploy/Config`.
|
||||
|
|
@ -35,7 +35,7 @@ def main(env):
|
|||
log = Logger.get_logger(__name__)
|
||||
log.info(f"Registered host: {registered_host()}")
|
||||
|
||||
menu.launch_openpype_menu()
|
||||
menu.launch_ayon_menu()
|
||||
|
||||
# Initiate a QTimer to check if Fusion is still alive every X interval
|
||||
# If Fusion is not found - kill itself
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class FusionCopyPrefsPrelaunch(PreLaunchHook):
|
|||
Prepares local Fusion profile directory, copies existing Fusion profile.
|
||||
This also sets FUSION MasterPrefs variable, which is used
|
||||
to apply Master.prefs file to override some Fusion profile settings to:
|
||||
- enable the OpenPype menu
|
||||
- enable the AYON menu
|
||||
- force Python 3 over Python 2
|
||||
- force English interface
|
||||
Master.prefs is defined in openpype/hosts/fusion/deploy/fusion_shared.prefs
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from ayon_core.hosts.fusion import (
|
|||
|
||||
class FusionPrelaunch(PreLaunchHook):
|
||||
"""
|
||||
Prepares OpenPype Fusion environment.
|
||||
Prepares AYON Fusion environment.
|
||||
Requires correct Python home variable to be defined in the environment
|
||||
settings for Fusion to point at a valid Python 3 build for Fusion.
|
||||
Python3 versions that are supported by Fusion:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
from ayon_core.lib import NumberDef
|
||||
|
||||
from ayon_core.hosts.fusion.api.plugin import GenericCreateSaver
|
||||
from ayon_core.hosts.fusion.api import get_current_comp
|
||||
|
||||
|
||||
class CreateImageSaver(GenericCreateSaver):
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ class CreateComposite(harmony.Creator):
|
|||
|
||||
name = "compositeDefault"
|
||||
label = "Composite"
|
||||
product_type = "mindbender.template"
|
||||
product_type = "template"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreateComposite, self).__init__(*args, **kwargs)
|
||||
|
|
@ -221,7 +221,7 @@ class CreateRender(harmony.Creator):
|
|||
|
||||
name = "writeDefault"
|
||||
label = "Write"
|
||||
product_type = "mindbender.imagesequence"
|
||||
product_type = "render"
|
||||
node_type = "WRITE"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
@ -304,7 +304,7 @@ class ExtractImage(pyblish.api.InstancePlugin):
|
|||
label = "Extract Image Sequence"
|
||||
order = pyblish.api.ExtractorOrder
|
||||
hosts = ["harmony"]
|
||||
families = ["mindbender.imagesequence"]
|
||||
families = ["render"]
|
||||
|
||||
def process(self, instance):
|
||||
project_path = harmony.send(
|
||||
|
|
@ -582,8 +582,16 @@ class ImageSequenceLoader(load.LoaderPlugin):
|
|||
"""Load images
|
||||
Stores the imported asset in a container named after the asset.
|
||||
"""
|
||||
product_types = {"mindbender.imagesequence"}
|
||||
product_types = {
|
||||
"shot",
|
||||
"render",
|
||||
"image",
|
||||
"plate",
|
||||
"reference",
|
||||
"review",
|
||||
}
|
||||
representations = ["*"]
|
||||
extensions = {"jpeg", "png", "jpg"}
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
files = []
|
||||
|
|
|
|||
|
|
@ -50,11 +50,11 @@ class ImportTemplateLoader(load.LoaderPlugin):
|
|||
self.__class__.__name__
|
||||
)
|
||||
|
||||
def update(self, container, context):
|
||||
pass
|
||||
def update(self, container, context):
|
||||
pass
|
||||
|
||||
def remove(self, container):
|
||||
pass
|
||||
def remove(self, container):
|
||||
pass
|
||||
|
||||
|
||||
class ImportWorkfileLoader(ImportTemplateLoader):
|
||||
|
|
|
|||
|
|
@ -632,7 +632,9 @@ def sync_avalon_data_to_workfile():
|
|||
project_name = get_current_project_name()
|
||||
|
||||
anatomy = Anatomy(project_name)
|
||||
work_template = anatomy.templates["work"]["path"]
|
||||
work_template = anatomy.get_template_item(
|
||||
"work", "default", "path"
|
||||
)
|
||||
work_root = anatomy.root_value_for_template(work_template)
|
||||
active_project_root = (
|
||||
os.path.join(work_root, project_name)
|
||||
|
|
@ -825,7 +827,7 @@ class PublishAction(QtWidgets.QAction):
|
|||
# root_node = hiero.core.nuke.RootNode()
|
||||
#
|
||||
# anatomy = Anatomy(get_current_project_name())
|
||||
# work_template = anatomy.templates["work"]["path"]
|
||||
# work_template = anatomy.get_template_item("work", "default", "path")
|
||||
# root_path = anatomy.root_value_for_template(work_template)
|
||||
#
|
||||
# nuke_script.addNode(root_node)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class CreatorWidget(QtWidgets.QDialog):
|
|||
| QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
self.setWindowTitle(name or "Pype Creator Input")
|
||||
self.setWindowTitle(name or "AYON Creator Input")
|
||||
self.resize(500, 700)
|
||||
|
||||
# Where inputs and labels are set
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ class CreateShotClip(phiero.Creator):
|
|||
|
||||
gui_tracks = [track.name()
|
||||
for track in phiero.get_current_sequence().videoTracks()]
|
||||
gui_name = "Pype publish attributes creator"
|
||||
gui_name = "AYON publish attributes creator"
|
||||
gui_info = "Define sequential rename and fill hierarchy data."
|
||||
gui_inputs = {
|
||||
"renameHierarchy": {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,6 @@ from ayon_core.lib import BoolDef
|
|||
from .lib import imprint, read, lsattr, add_self_publish_button
|
||||
|
||||
|
||||
class OpenPypeCreatorError(CreatorError):
|
||||
pass
|
||||
|
||||
|
||||
class Creator(LegacyCreator):
|
||||
"""Creator plugin to create instances in Houdini
|
||||
|
||||
|
|
@ -92,8 +88,8 @@ class Creator(LegacyCreator):
|
|||
|
||||
except hou.Error as er:
|
||||
six.reraise(
|
||||
OpenPypeCreatorError,
|
||||
OpenPypeCreatorError("Creator error: {}".format(er)),
|
||||
CreatorError,
|
||||
CreatorError("Creator error: {}".format(er)),
|
||||
sys.exc_info()[2])
|
||||
|
||||
|
||||
|
|
@ -147,7 +143,6 @@ class HoudiniCreatorBase(object):
|
|||
def create_instance_node(
|
||||
folder_path, node_name, parent, node_type="geometry"
|
||||
):
|
||||
# type: (str, str, str) -> hou.Node
|
||||
"""Create node representing instance.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -210,8 +205,8 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase):
|
|||
|
||||
except hou.Error as er:
|
||||
six.reraise(
|
||||
OpenPypeCreatorError,
|
||||
OpenPypeCreatorError("Creator error: {}".format(er)),
|
||||
CreatorError,
|
||||
CreatorError("Creator error: {}".format(er)),
|
||||
sys.exc_info()[2])
|
||||
|
||||
def lock_parameters(self, node, parameters):
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"""Creator plugin for creating publishable Houdini Digital Assets."""
|
||||
import ayon_api
|
||||
|
||||
from ayon_core.pipeline import CreatorError
|
||||
from ayon_core.hosts.houdini.api import plugin
|
||||
import hou
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
maintain_selection = False
|
||||
|
||||
def _check_existing(self, folder_path, product_name):
|
||||
# type: (str) -> bool
|
||||
# type: (str, str) -> bool
|
||||
"""Check if existing product name versions already exists."""
|
||||
# Get all products of the current folder
|
||||
project_name = self.project_name
|
||||
|
|
@ -52,7 +53,7 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
# if node type has not its definition, it is not user
|
||||
# created hda. We test if hda can be created from the node.
|
||||
if not to_hda.canCreateDigitalAsset():
|
||||
raise plugin.OpenPypeCreatorError(
|
||||
raise CreatorError(
|
||||
"cannot create hda from node {}".format(to_hda))
|
||||
|
||||
hda_node = to_hda.createDigitalAsset(
|
||||
|
|
@ -61,7 +62,7 @@ class CreateHDA(plugin.HoudiniCreator):
|
|||
)
|
||||
hda_node.layoutChildren()
|
||||
elif self._check_existing(folder_path, node_name):
|
||||
raise plugin.OpenPypeCreatorError(
|
||||
raise CreatorError(
|
||||
("product {} is already published with different HDA"
|
||||
"definition.").format(node_name))
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
"""Creator plugin to create Redshift ROP."""
|
||||
import hou # noqa
|
||||
|
||||
from ayon_core.pipeline import CreatorError
|
||||
from ayon_core.hosts.houdini.api import plugin
|
||||
from ayon_core.lib import EnumDef, BoolDef
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
|||
"Redshift_IPR", node_name=f"{basename}_IPR"
|
||||
)
|
||||
except hou.OperationFailed as e:
|
||||
raise plugin.OpenPypeCreatorError(
|
||||
raise CreatorError(
|
||||
(
|
||||
"Cannot create Redshift node. Is Redshift "
|
||||
"installed and enabled?"
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import hou
|
||||
|
||||
from ayon_core.hosts.houdini.api import plugin
|
||||
from ayon_core.pipeline import CreatedInstance
|
||||
from ayon_core.pipeline import CreatedInstance, CreatorError
|
||||
from ayon_core.lib import EnumDef, BoolDef
|
||||
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class CreateVrayROP(plugin.HoudiniCreator):
|
|||
"vray", node_name=basename + "_IPR"
|
||||
)
|
||||
except hou.OperationFailed:
|
||||
raise plugin.OpenPypeCreatorError(
|
||||
raise CreatorError(
|
||||
"Cannot create Vray render node. "
|
||||
"Make sure Vray installed and enabled!"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
from ayon_core.pipeline import (
|
||||
load,
|
||||
|
|
@ -44,7 +45,14 @@ def get_image_avalon_container():
|
|||
class ImageLoader(load.LoaderPlugin):
|
||||
"""Load images into COP2"""
|
||||
|
||||
product_types = {"imagesequence"}
|
||||
product_types = {
|
||||
"imagesequence",
|
||||
"review",
|
||||
"render",
|
||||
"plate",
|
||||
"image",
|
||||
"online",
|
||||
}
|
||||
label = "Load Image (COP2)"
|
||||
representations = ["*"]
|
||||
order = -10
|
||||
|
|
@ -55,10 +63,8 @@ class ImageLoader(load.LoaderPlugin):
|
|||
def load(self, context, name=None, namespace=None, data=None):
|
||||
|
||||
# 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._get_file_sequence(file_path)
|
||||
path = self.filepath_from_context(context)
|
||||
path = self.format_path(path, representation=context["representation"])
|
||||
|
||||
# Get the root node
|
||||
parent = get_image_avalon_container()
|
||||
|
|
@ -70,7 +76,10 @@ class ImageLoader(load.LoaderPlugin):
|
|||
node = parent.createNode("file", node_name=node_name)
|
||||
node.moveToGoodPosition()
|
||||
|
||||
node.setParms({"filename1": file_path})
|
||||
parms = {"filename1": path}
|
||||
parms.update(self.get_colorspace_parms(context["representation"]))
|
||||
|
||||
node.setParms(parms)
|
||||
|
||||
# Imprint it manually
|
||||
data = {
|
||||
|
|
@ -93,16 +102,17 @@ class ImageLoader(load.LoaderPlugin):
|
|||
|
||||
# Update the file path
|
||||
file_path = get_representation_path(repre_entity)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
file_path = self._get_file_sequence(file_path)
|
||||
file_path = self.format_path(file_path, repre_entity)
|
||||
|
||||
parms = {
|
||||
"filename1": file_path,
|
||||
"representation": repre_entity["id"],
|
||||
}
|
||||
|
||||
parms.update(self.get_colorspace_parms(repre_entity))
|
||||
|
||||
# Update attributes
|
||||
node.setParms(
|
||||
{
|
||||
"filename1": file_path,
|
||||
"representation": repre_entity["id"],
|
||||
}
|
||||
)
|
||||
node.setParms(parms)
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
|
|
@ -119,14 +129,58 @@ class ImageLoader(load.LoaderPlugin):
|
|||
if not parent.children():
|
||||
parent.destroy()
|
||||
|
||||
def _get_file_sequence(self, file_path):
|
||||
root = os.path.dirname(file_path)
|
||||
files = sorted(os.listdir(root))
|
||||
@staticmethod
|
||||
def format_path(path, representation):
|
||||
"""Format file path correctly for single image or sequence."""
|
||||
if not os.path.exists(path):
|
||||
raise RuntimeError("Path does not exist: %s" % path)
|
||||
|
||||
first_fname = files[0]
|
||||
prefix, padding, suffix = first_fname.rsplit(".", 2)
|
||||
fname = ".".join([prefix, "$F{}".format(len(padding)), suffix])
|
||||
return os.path.join(root, fname).replace("\\", "/")
|
||||
ext = os.path.splitext(path)[-1]
|
||||
|
||||
def switch(self, container, context):
|
||||
self.update(container, context)
|
||||
is_sequence = bool(representation["context"].get("frame"))
|
||||
# The path is either a single file or sequence in a folder.
|
||||
if not is_sequence:
|
||||
filename = path
|
||||
else:
|
||||
filename = re.sub(r"(.*)\.(\d+){}$".format(re.escape(ext)),
|
||||
"\\1.$F4{}".format(ext),
|
||||
path)
|
||||
|
||||
filename = os.path.join(path, filename)
|
||||
|
||||
filename = os.path.normpath(filename)
|
||||
filename = filename.replace("\\", "/")
|
||||
|
||||
return filename
|
||||
|
||||
def get_colorspace_parms(self, representation: dict) -> dict:
|
||||
"""Return the color space parameters.
|
||||
|
||||
Returns the values for the colorspace parameters on the node if there
|
||||
is colorspace data on the representation.
|
||||
|
||||
Arguments:
|
||||
representation (dict): The representation entity.
|
||||
|
||||
Returns:
|
||||
dict: Parm to value mapping if colorspace data is defined.
|
||||
|
||||
"""
|
||||
# Using OCIO colorspace on COP2 File node is only supported in Hou 20+
|
||||
major, _, _ = hou.applicationVersion()
|
||||
if major < 20:
|
||||
return {}
|
||||
|
||||
data = representation.get("data", {}).get("colorspaceData", {})
|
||||
if not data:
|
||||
return {}
|
||||
|
||||
colorspace = data["colorspace"]
|
||||
if colorspace:
|
||||
return {
|
||||
"colorspace": 3, # Use OpenColorIO
|
||||
"ocio_space": colorspace
|
||||
}
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mainMenu>
|
||||
<menuBar>
|
||||
<subMenu id="openpype_menu">
|
||||
<subMenu id="ayon_menu">
|
||||
<labelExpression><![CDATA[
|
||||
import os
|
||||
return os.environ.get("AYON_MENU_LABEL") or "AYON"
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ from ayon_core.tools.utils import host_tools
|
|||
from ayon_core.hosts.max.api import lib
|
||||
|
||||
|
||||
class OpenPypeMenu(object):
|
||||
"""Object representing OpenPype/AYON menu.
|
||||
class AYONMenu(object):
|
||||
"""Object representing AYON menu.
|
||||
|
||||
This is using "hack" to inject itself before "Help" menu of 3dsmax.
|
||||
For some reason `postLoadingMenus` event doesn't fire, and main menu
|
||||
|
|
@ -39,7 +39,7 @@ class OpenPypeMenu(object):
|
|||
|
||||
self._counter = 0
|
||||
self._timer.stop()
|
||||
self.build_openpype_menu()
|
||||
self._build_ayon_menu()
|
||||
|
||||
@staticmethod
|
||||
def get_main_widget():
|
||||
|
|
@ -50,8 +50,8 @@ class OpenPypeMenu(object):
|
|||
"""Get main Menubar by 3dsmax main window."""
|
||||
return list(self.main_widget.findChildren(QtWidgets.QMenuBar))[0]
|
||||
|
||||
def get_or_create_openpype_menu(
|
||||
self, name: str = "&Openpype",
|
||||
def _get_or_create_ayon_menu(
|
||||
self, name: str = "&AYON",
|
||||
before: str = "&Help") -> QtWidgets.QAction:
|
||||
"""Create AYON menu.
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ class OpenPypeMenu(object):
|
|||
help_action = None
|
||||
for item in menu_items:
|
||||
if name in item.title():
|
||||
# we already have OpenPype menu
|
||||
# we already have AYON menu
|
||||
return item
|
||||
|
||||
if before in item.title():
|
||||
|
|
@ -85,50 +85,50 @@ class OpenPypeMenu(object):
|
|||
self.menu = op_menu
|
||||
return op_menu
|
||||
|
||||
def build_openpype_menu(self) -> QtWidgets.QAction:
|
||||
def _build_ayon_menu(self) -> QtWidgets.QAction:
|
||||
"""Build items in AYON menu."""
|
||||
openpype_menu = self.get_or_create_openpype_menu()
|
||||
load_action = QtWidgets.QAction("Load...", openpype_menu)
|
||||
ayon_menu = self._get_or_create_ayon_menu()
|
||||
load_action = QtWidgets.QAction("Load...", ayon_menu)
|
||||
load_action.triggered.connect(self.load_callback)
|
||||
openpype_menu.addAction(load_action)
|
||||
ayon_menu.addAction(load_action)
|
||||
|
||||
publish_action = QtWidgets.QAction("Publish...", openpype_menu)
|
||||
publish_action = QtWidgets.QAction("Publish...", ayon_menu)
|
||||
publish_action.triggered.connect(self.publish_callback)
|
||||
openpype_menu.addAction(publish_action)
|
||||
ayon_menu.addAction(publish_action)
|
||||
|
||||
manage_action = QtWidgets.QAction("Manage...", openpype_menu)
|
||||
manage_action = QtWidgets.QAction("Manage...", ayon_menu)
|
||||
manage_action.triggered.connect(self.manage_callback)
|
||||
openpype_menu.addAction(manage_action)
|
||||
ayon_menu.addAction(manage_action)
|
||||
|
||||
library_action = QtWidgets.QAction("Library...", openpype_menu)
|
||||
library_action = QtWidgets.QAction("Library...", ayon_menu)
|
||||
library_action.triggered.connect(self.library_callback)
|
||||
openpype_menu.addAction(library_action)
|
||||
ayon_menu.addAction(library_action)
|
||||
|
||||
openpype_menu.addSeparator()
|
||||
ayon_menu.addSeparator()
|
||||
|
||||
workfiles_action = QtWidgets.QAction("Work Files...", openpype_menu)
|
||||
workfiles_action = QtWidgets.QAction("Work Files...", ayon_menu)
|
||||
workfiles_action.triggered.connect(self.workfiles_callback)
|
||||
openpype_menu.addAction(workfiles_action)
|
||||
ayon_menu.addAction(workfiles_action)
|
||||
|
||||
openpype_menu.addSeparator()
|
||||
ayon_menu.addSeparator()
|
||||
|
||||
res_action = QtWidgets.QAction("Set Resolution", openpype_menu)
|
||||
res_action = QtWidgets.QAction("Set Resolution", ayon_menu)
|
||||
res_action.triggered.connect(self.resolution_callback)
|
||||
openpype_menu.addAction(res_action)
|
||||
ayon_menu.addAction(res_action)
|
||||
|
||||
frame_action = QtWidgets.QAction("Set Frame Range", openpype_menu)
|
||||
frame_action = QtWidgets.QAction("Set Frame Range", ayon_menu)
|
||||
frame_action.triggered.connect(self.frame_range_callback)
|
||||
openpype_menu.addAction(frame_action)
|
||||
ayon_menu.addAction(frame_action)
|
||||
|
||||
colorspace_action = QtWidgets.QAction("Set Colorspace", openpype_menu)
|
||||
colorspace_action = QtWidgets.QAction("Set Colorspace", ayon_menu)
|
||||
colorspace_action.triggered.connect(self.colorspace_callback)
|
||||
openpype_menu.addAction(colorspace_action)
|
||||
ayon_menu.addAction(colorspace_action)
|
||||
|
||||
unit_scale_action = QtWidgets.QAction("Set Unit Scale", openpype_menu)
|
||||
unit_scale_action = QtWidgets.QAction("Set Unit Scale", ayon_menu)
|
||||
unit_scale_action.triggered.connect(self.unit_scale_callback)
|
||||
openpype_menu.addAction(unit_scale_action)
|
||||
ayon_menu.addAction(unit_scale_action)
|
||||
|
||||
return openpype_menu
|
||||
return ayon_menu
|
||||
|
||||
def load_callback(self):
|
||||
"""Callback to show Loader tool."""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Pipeline tools for OpenPype Houdini integration."""
|
||||
"""Pipeline tools for AYON 3ds max integration."""
|
||||
import os
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
|
|
@ -14,7 +14,7 @@ from ayon_core.pipeline import (
|
|||
AVALON_CONTAINER_ID,
|
||||
AYON_CONTAINER_ID,
|
||||
)
|
||||
from ayon_core.hosts.max.api.menu import OpenPypeMenu
|
||||
from ayon_core.hosts.max.api.menu import AYONMenu
|
||||
from ayon_core.hosts.max.api import lib
|
||||
from ayon_core.hosts.max.api.plugin import MS_CUSTOM_ATTRIB
|
||||
from ayon_core.hosts.max import MAX_HOST_DIR
|
||||
|
|
@ -48,7 +48,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
register_creator_plugin_path(CREATE_PATH)
|
||||
|
||||
# self._register_callbacks()
|
||||
self.menu = OpenPypeMenu()
|
||||
self.menu = AYONMenu()
|
||||
|
||||
self._has_been_setup = True
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
|
||||
def _deferred_menu_creation(self):
|
||||
self.log.info("Building menu ...")
|
||||
self.menu = OpenPypeMenu()
|
||||
self.menu = AYONMenu()
|
||||
|
||||
@staticmethod
|
||||
def create_context_node():
|
||||
|
|
@ -148,7 +148,7 @@ attributes "OpenPypeContext"
|
|||
|
||||
|
||||
def ls() -> list:
|
||||
"""Get all OpenPype instances."""
|
||||
"""Get all AYON containers."""
|
||||
objs = rt.objects
|
||||
containers = [
|
||||
obj for obj in objs
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""3dsmax specific Avalon/Pyblish plugin definitions."""
|
||||
"""3dsmax specific AYON/Pyblish plugin definitions."""
|
||||
from abc import ABCMeta
|
||||
|
||||
import six
|
||||
|
|
@ -156,10 +156,6 @@ MS_CUSTOM_ATTRIB = """attributes "openPypeData"
|
|||
)"""
|
||||
|
||||
|
||||
class OpenPypeCreatorError(CreatorError):
|
||||
pass
|
||||
|
||||
|
||||
class MaxCreatorBase(object):
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from ayon_core.lib.applications import PreLaunchHook, LaunchTypes
|
|||
|
||||
|
||||
class ForceStartupScript(PreLaunchHook):
|
||||
"""Inject OpenPype environment to 3ds max.
|
||||
"""Inject AYON environment to 3ds max.
|
||||
|
||||
Note that this works in combination whit 3dsmax startup script that
|
||||
is translating it back to PYTHONPATH for cases when 3dsmax drops PYTHONPATH
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from ayon_core.lib.applications import PreLaunchHook, LaunchTypes
|
|||
|
||||
|
||||
class InjectPythonPath(PreLaunchHook):
|
||||
"""Inject OpenPype environment to 3dsmax.
|
||||
"""Inject AYON environment to 3dsmax.
|
||||
|
||||
Note that this works in combination whit 3dsmax startup script that
|
||||
is translating it back to PYTHONPATH for cases when 3dsmax drops PYTHONPATH
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin,
|
|||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
bullet_point_invalid_statement = "\n".join(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
-- OpenPype Init Script
|
||||
-- AYON Init Script
|
||||
(
|
||||
local sysPath = dotNetClass "System.IO.Path"
|
||||
local sysDir = dotNetClass "System.IO.Directory"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""OpenPype script commands to be used directly in Maya."""
|
||||
"""AYON script commands to be used directly in Maya."""
|
||||
from maya import cmds
|
||||
|
||||
from ayon_api import get_project, get_folder_by_path
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@ def override_toolbox_ui():
|
|||
|
||||
controls.append(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_lookmanager",
|
||||
"ayon_toolbox_lookmanager",
|
||||
annotation="Look Manager",
|
||||
label="Look Manager",
|
||||
image=os.path.join(icons, "lookmanager.png"),
|
||||
|
|
@ -122,7 +122,7 @@ def override_toolbox_ui():
|
|||
|
||||
controls.append(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_workfiles",
|
||||
"ayon_toolbox_workfiles",
|
||||
annotation="Work Files",
|
||||
label="Work Files",
|
||||
image=os.path.join(icons, "workfiles.png"),
|
||||
|
|
@ -137,7 +137,7 @@ def override_toolbox_ui():
|
|||
|
||||
controls.append(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_loader",
|
||||
"ayon_toolbox_loader",
|
||||
annotation="Loader",
|
||||
label="Loader",
|
||||
image=os.path.join(icons, "loader.png"),
|
||||
|
|
@ -152,7 +152,7 @@ def override_toolbox_ui():
|
|||
|
||||
controls.append(
|
||||
cmds.iconTextButton(
|
||||
"pype_toolbox_manager",
|
||||
"ayon_toolbox_manager",
|
||||
annotation="Inventory",
|
||||
label="Inventory",
|
||||
image=os.path.join(icons, "inventory.png"),
|
||||
|
|
|
|||
|
|
@ -2931,13 +2931,13 @@ def bake_to_world_space(nodes,
|
|||
|
||||
|
||||
def load_capture_preset(data):
|
||||
"""Convert OpenPype Extract Playblast settings to `capture` arguments
|
||||
"""Convert AYON Extract Playblast settings to `capture` arguments
|
||||
|
||||
Input data is the settings from:
|
||||
`project_settings/maya/publish/ExtractPlayblast/capture_preset`
|
||||
|
||||
Args:
|
||||
data (dict): Capture preset settings from OpenPype settings
|
||||
data (dict): Capture preset settings from AYON settings
|
||||
|
||||
Returns:
|
||||
dict: `capture.capture` compatible keyword arguments
|
||||
|
|
@ -3288,7 +3288,7 @@ def set_colorspace():
|
|||
else:
|
||||
# TODO: deprecated code from 3.15.5 - remove
|
||||
# Maya 2022+ introduces new OCIO v2 color management settings that
|
||||
# can override the old color management preferences. OpenPype has
|
||||
# can override the old color management preferences. AYON has
|
||||
# separate settings for both so we fall back when necessary.
|
||||
use_ocio_v2 = imageio["colorManagementPreference_v2"]["enabled"]
|
||||
if use_ocio_v2 and not ocio_v2_support:
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
https://github.com/Colorbleed/colorbleed-config/blob/acre/colorbleed/maya/lib_rendersetup.py
|
||||
Credits: Roy Nieterau (BigRoy) / Colorbleed
|
||||
Modified for use in OpenPype
|
||||
Modified for use in AYON
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ def get_context_label():
|
|||
|
||||
def install(project_settings):
|
||||
if cmds.about(batch=True):
|
||||
log.info("Skipping openpype.menu initialization in batch mode..")
|
||||
log.info("Skipping AYON menu initialization in batch mode..")
|
||||
return
|
||||
|
||||
def add_menu():
|
||||
|
|
@ -261,7 +261,7 @@ def popup():
|
|||
|
||||
|
||||
def update_menu_task_label():
|
||||
"""Update the task label in Avalon menu to current session"""
|
||||
"""Update the task label in AYON menu to current session"""
|
||||
|
||||
if IS_HEADLESS:
|
||||
return
|
||||
|
|
|
|||
|
|
@ -361,13 +361,13 @@ def parse_container(container):
|
|||
|
||||
|
||||
def _ls():
|
||||
"""Yields Avalon container node names.
|
||||
"""Yields AYON container node names.
|
||||
|
||||
Used by `ls()` to retrieve the nodes and then query the full container's
|
||||
data.
|
||||
|
||||
Yields:
|
||||
str: Avalon container node name (objectSet)
|
||||
str: AYON container node name (objectSet)
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -384,7 +384,7 @@ def _ls():
|
|||
}
|
||||
|
||||
# Iterate over all 'set' nodes in the scene to detect whether
|
||||
# they have the avalon container ".id" attribute.
|
||||
# they have the ayon container ".id" attribute.
|
||||
fn_dep = om.MFnDependencyNode()
|
||||
iterator = om.MItDependencyNodes(om.MFn.kSet)
|
||||
for mobject in _maya_iterate(iterator):
|
||||
|
|
@ -673,7 +673,7 @@ def workfile_save_before_xgen(event):
|
|||
switching context.
|
||||
|
||||
Args:
|
||||
event (Event) - openpype/lib/events.py
|
||||
event (Event) - ayon_core/lib/events.py
|
||||
"""
|
||||
if not cmds.pluginInfo("xgenToolkit", query=True, loaded=True):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -899,7 +899,7 @@ class ReferenceLoader(Loader):
|
|||
cmds.disconnectAttr(input, node_attr)
|
||||
cmds.setAttr(node_attr, data["value"])
|
||||
|
||||
# Fix PLN-40 for older containers created with Avalon that had the
|
||||
# Fix PLN-40 for older containers created with AYON that had the
|
||||
# `.verticesOnlySet` set to True.
|
||||
if cmds.getAttr("{}.verticesOnlySet".format(node)):
|
||||
self.log.info("Setting %s.verticesOnlySet to False", node)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Export Maya nodes from Render Setup layer as if flattened in that layer instead
|
|||
of exporting the defaultRenderLayer as Maya forces by default
|
||||
|
||||
Credits: Roy Nieterau (BigRoy) / Colorbleed
|
||||
Modified for use in OpenPype
|
||||
Modified for use in AYON
|
||||
|
||||
"""
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ def load_package(filepath, name, namespace=None):
|
|||
containers.append(container)
|
||||
|
||||
# TODO: Do we want to cripple? Or do we want to add a 'parent' parameter?
|
||||
# Cripple the original avalon containers so they don't show up in the
|
||||
# Cripple the original AYON containers so they don't show up in the
|
||||
# manager
|
||||
# for container in containers:
|
||||
# cmds.setAttr("%s.id" % container,
|
||||
|
|
@ -175,7 +175,7 @@ def _add(instance, representation_id, loaders, namespace, root="|"):
|
|||
namespace (str):
|
||||
|
||||
Returns:
|
||||
str: The created Avalon container.
|
||||
str: The created AYON container.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ def _instances_by_namespace(data):
|
|||
|
||||
|
||||
def get_contained_containers(container):
|
||||
"""Get the Avalon containers in this container
|
||||
"""Get the AYON containers in this container
|
||||
|
||||
Args:
|
||||
container (dict): The container dict.
|
||||
|
|
@ -256,7 +256,7 @@ def get_contained_containers(container):
|
|||
|
||||
from .pipeline import parse_container
|
||||
|
||||
# Get avalon containers in this package setdress container
|
||||
# Get AYON containers in this package setdress container
|
||||
containers = []
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
for node in cmds.ls(members, type="objectSet"):
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class MayaLegacyConvertor(ProductConvertorPlugin,
|
|||
|
||||
Its limitation is that you can have multiple creators creating product
|
||||
of the same type and there is no way to handle it. This code should
|
||||
nevertheless cover all creators that came with OpenPype.
|
||||
nevertheless cover all creators that came with AYON.
|
||||
|
||||
"""
|
||||
identifier = "io.openpype.creators.maya.legacy"
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class LoadVDBtoArnold(load.LoaderPlugin):
|
|||
|
||||
from maya import cmds
|
||||
|
||||
# Get all members of the avalon container, ensure they are unlocked
|
||||
# Get all members of the AYON container, ensure they are unlocked
|
||||
# and delete everything
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
cmds.lockNode(members, lock=False)
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ class LoadVDBtoRedShift(load.LoaderPlugin):
|
|||
def remove(self, container):
|
||||
from maya import cmds
|
||||
|
||||
# Get all members of the avalon container, ensure they are unlocked
|
||||
# Get all members of the AYON container, ensure they are unlocked
|
||||
# and delete everything
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
cmds.lockNode(members, lock=False)
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ class LoadVDBtoVRay(load.LoaderPlugin):
|
|||
|
||||
def remove(self, container):
|
||||
|
||||
# Get all members of the avalon container, ensure they are unlocked
|
||||
# Get all members of the AYON container, ensure they are unlocked
|
||||
# and delete everything
|
||||
members = cmds.sets(container['objectName'], query=True)
|
||||
cmds.lockNode(members, lock=False)
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ loader will use them instead of native vray vrmesh format.
|
|||
"""
|
||||
import os
|
||||
|
||||
from ayon_api import get_representation_by_name
|
||||
import maya.cmds as cmds
|
||||
|
||||
import ayon_api
|
||||
from ayon_core.settings import get_project_settings
|
||||
from ayon_core.pipeline import (
|
||||
load,
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ def iter_history(nodes,
|
|||
def collect_input_containers(containers, nodes):
|
||||
"""Collect containers that contain any of the node in `nodes`.
|
||||
|
||||
This will return any loaded Avalon container that contains at least one of
|
||||
the nodes. As such, the Avalon container is an input for it. Or in short,
|
||||
This will return any loaded AYON container that contains at least one of
|
||||
the nodes. As such, the AYON container is an input for it. Or in short,
|
||||
there are member nodes of that container.
|
||||
|
||||
Returns:
|
||||
list: Input avalon containers
|
||||
list: Input loaded containers
|
||||
|
||||
"""
|
||||
# Assume the containers have collected their cached '_members' data
|
||||
|
|
|
|||
|
|
@ -40,9 +40,11 @@ class _NodeTypeAttrib(object):
|
|||
return "{}.{}".format(node, self.colour_space)
|
||||
|
||||
def __str__(self):
|
||||
return "_NodeTypeAttrib(name={}, fname={}, "
|
||||
"computed_fname={}, colour_space={})".format(
|
||||
self.name, self.fname, self.computed_fname, self.colour_space)
|
||||
return (
|
||||
"_NodeTypeAttrib(name={}, fname={}, "
|
||||
"computed_fname={}, colour_space={})".format(
|
||||
self.name, self.fname, self.computed_fname, self.colour_space)
|
||||
)
|
||||
|
||||
|
||||
NODETYPES = {
|
||||
|
|
|
|||
|
|
@ -106,10 +106,10 @@ class TextureProcessor:
|
|||
self.log = log
|
||||
|
||||
def apply_settings(self, project_settings):
|
||||
"""Apply OpenPype system/project settings to the TextureProcessor
|
||||
"""Apply AYON system/project settings to the TextureProcessor
|
||||
|
||||
Args:
|
||||
project_settings (dict): OpenPype project settings
|
||||
project_settings (dict): AYON project settings
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
|
@ -278,7 +278,7 @@ class MakeTX(TextureProcessor):
|
|||
"""Process the texture.
|
||||
|
||||
This function requires the `maketx` executable to be available in an
|
||||
OpenImageIO toolset detectable by OpenPype.
|
||||
OpenImageIO toolset detectable by AYON.
|
||||
|
||||
Args:
|
||||
source (str): Path to source file.
|
||||
|
|
|
|||
|
|
@ -128,9 +128,11 @@ class ExtractWorkfileXgen(publish.Extractor):
|
|||
alembic_files.append(alembic_file)
|
||||
|
||||
template_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
published_maya_path = StringTemplate(
|
||||
instance.context.data["anatomy"].templates["publish"]["file"]
|
||||
).format(template_data)
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
publish_template = anatomy.get_template_item(
|
||||
"publish", "default", "file"
|
||||
)
|
||||
published_maya_path = publish_template.format(template_data)
|
||||
published_basename, _ = os.path.splitext(published_maya_path)
|
||||
|
||||
for source in alembic_files:
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ class ExtractXgen(publish.Extractor):
|
|||
# Get published xgen file name.
|
||||
template_data = copy.deepcopy(instance.data["anatomyData"])
|
||||
template_data.update({"ext": "xgen"})
|
||||
templates = instance.context.data["anatomy"].templates["publish"]
|
||||
xgen_filename = StringTemplate(templates["file"]).format(template_data)
|
||||
anatomy = instance.context.data["anatomy"]
|
||||
file_template = anatomy.get_template_item("publish", "default", "file")
|
||||
xgen_filename = file_template.format(template_data)
|
||||
|
||||
xgen_path = os.path.join(
|
||||
self.staging_dir(instance), xgen_filename
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ import pyblish.api
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
ValidateContentsOrder
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
from maya import cmds
|
||||
|
||||
|
||||
class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin):
|
||||
class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate all nodes in skeletonAnim_SET are referenced"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
|
|
@ -16,8 +18,11 @@ class ValidateAnimatedReferenceRig(pyblish.api.InstancePlugin):
|
|||
label = "Animated Reference Rig"
|
||||
accepted_controllers = ["transform", "locator"]
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
animated_sets = instance.data.get("animated_skeleton", [])
|
||||
if not animated_sets:
|
||||
self.log.debug(
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import pyblish.api
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
ValidateContentsOrder
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateAnimationContent(pyblish.api.InstancePlugin):
|
||||
class ValidateAnimationContent(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Adheres to the content of 'animation' product type
|
||||
|
||||
- Must have collected `out_hierarchy` data.
|
||||
|
|
@ -19,6 +21,7 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin):
|
|||
families = ["animation"]
|
||||
label = "Animation Content"
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -48,6 +51,8 @@ class ValidateAnimationContent(pyblish.api.InstancePlugin):
|
|||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@ from ayon_core.hosts.maya.api import lib
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin):
|
||||
class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate if deformed shapes have related IDs to the original shapes
|
||||
|
||||
When a deformer is applied in the scene on a referenced mesh that already
|
||||
|
|
@ -28,10 +30,12 @@ class ValidateOutRelatedNodeIds(pyblish.api.InstancePlugin):
|
|||
ayon_core.hosts.maya.api.action.SelectInvalidAction,
|
||||
RepairAction
|
||||
]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all meshes"""
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
# Ensure all nodes have a cbId and a related ID to the original shapes
|
||||
# if a deformer has been created on the shape
|
||||
invalid = self.get_invalid(instance)
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import pyblish.api
|
||||
from ayon_core.hosts.maya.api import lib
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder, PublishValidationError, RepairAction
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError,
|
||||
RepairAction,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin):
|
||||
class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate Arnold Scene Source Cbid.
|
||||
|
||||
It is required for the proxy and content nodes to share the same cbid.
|
||||
|
|
@ -16,6 +20,7 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin):
|
|||
families = ["ass"]
|
||||
label = "Validate Arnold Scene Source CBID"
|
||||
actions = [RepairAction]
|
||||
optional = False
|
||||
|
||||
@staticmethod
|
||||
def _get_nodes_by_name(nodes):
|
||||
|
|
@ -55,6 +60,8 @@ class ValidateArnoldSceneSourceCbid(pyblish.api.InstancePlugin):
|
|||
return invalid_couples
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
# Proxy validation.
|
||||
if not instance.data.get("proxy", []):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import pyblish.api
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateAssRelativePaths(pyblish.api.InstancePlugin):
|
||||
class ValidateAssRelativePaths(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure exporting ass file has set relative texture paths"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
|
|
@ -20,8 +22,11 @@ class ValidateAssRelativePaths(pyblish.api.InstancePlugin):
|
|||
families = ['ass']
|
||||
label = "ASS has relative texture paths"
|
||||
actions = [RepairAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
# we cannot ask this until user open render settings as
|
||||
# `defaultArnoldRenderOptions` doesn't exist
|
||||
errors = []
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ import pyblish.api
|
|||
import maya.cmds as cmds
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateAssemblyName(pyblish.api.InstancePlugin):
|
||||
class ValidateAssemblyName(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
""" Ensure Assembly name ends with `GRP`
|
||||
|
||||
Check if assembly name ends with `_GRP` string.
|
||||
|
|
@ -17,6 +19,7 @@ class ValidateAssemblyName(pyblish.api.InstancePlugin):
|
|||
families = ["assembly"]
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
active = False
|
||||
optional = True
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -47,7 +50,8 @@ class ValidateAssemblyName(pyblish.api.InstancePlugin):
|
|||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError("Found {} invalid named assembly "
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import pyblish.api
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin):
|
||||
class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure namespaces are not nested
|
||||
|
||||
In the outliner an item in a normal namespace looks as following:
|
||||
|
|
@ -20,9 +22,11 @@ class ValidateAssemblyNamespaces(pyblish.api.InstancePlugin):
|
|||
order = pyblish.api.ValidatorOrder
|
||||
families = ["assembly"]
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
self.log.debug("Checking namespace for %s" % instance.name)
|
||||
if self.get_invalid(instance):
|
||||
raise PublishValidationError("Nested namespaces found")
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@ import pyblish.api
|
|||
from maya import cmds
|
||||
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import PublishValidationError, RepairAction
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
RepairAction,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin):
|
||||
class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Verify only root nodes of the loaded asset have transformations.
|
||||
|
||||
Note: This check is temporary and is subject to change.
|
||||
|
|
@ -34,7 +39,11 @@ class ValidateAssemblyModelTransforms(pyblish.api.InstancePlugin):
|
|||
" This can alter the look of your scene. "
|
||||
"Are you sure you want to continue?")
|
||||
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@ from maya import cmds
|
|||
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError, ValidateContentsOrder)
|
||||
PublishValidationError,
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateCameraAttributes(pyblish.api.InstancePlugin):
|
||||
class ValidateCameraAttributes(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validates Camera has no invalid attribute keys or values.
|
||||
|
||||
The Alembic file format does not a specific subset of attributes as such
|
||||
|
|
@ -20,6 +24,7 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin):
|
|||
hosts = ['maya']
|
||||
label = 'Camera Attributes'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
DEFAULTS = [
|
||||
("filmFitOffset", 0.0),
|
||||
|
|
@ -62,7 +67,8 @@ class ValidateCameraAttributes(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance"""
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ from maya import cmds
|
|||
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError, ValidateContentsOrder)
|
||||
PublishValidationError,
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin)
|
||||
|
||||
|
||||
class ValidateCameraContents(pyblish.api.InstancePlugin):
|
||||
class ValidateCameraContents(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validates Camera instance contents.
|
||||
|
||||
A Camera instance may only hold a SINGLE camera's transform, nothing else.
|
||||
|
|
@ -22,6 +25,7 @@ class ValidateCameraContents(pyblish.api.InstancePlugin):
|
|||
label = 'Camera Contents'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
validate_shapes = True
|
||||
optional = False
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -71,7 +75,8 @@ class ValidateCameraContents(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance"""
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError("Invalid camera contents: "
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import pyblish.api
|
||||
|
||||
from maya import cmds
|
||||
from ayon_core.pipeline.publish import context_plugin_should_run
|
||||
from ayon_core.pipeline.publish import (
|
||||
context_plugin_should_run,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin):
|
||||
class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate if current render layer has a renderable camera
|
||||
|
||||
There is a bug in Redshift which occurs when the current render layer
|
||||
|
|
@ -20,9 +23,11 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin):
|
|||
order = pyblish.api.ValidatorOrder
|
||||
hosts = ["maya"]
|
||||
families = ["renderlayer"]
|
||||
optional = False
|
||||
|
||||
def process(self, context):
|
||||
|
||||
if not self.is_active(context.data):
|
||||
return
|
||||
# Workaround bug pyblish-base#250
|
||||
if not context_plugin_should_run(self, context):
|
||||
return
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ from ayon_core.pipeline.publish import (
|
|||
RepairAction,
|
||||
ValidateContentsOrder
|
||||
)
|
||||
from ayon_core.pipeline import PublishValidationError
|
||||
from ayon_core.pipeline import PublishValidationError, OptionalPyblishPluginMixin
|
||||
|
||||
|
||||
class ValidateGLSLMaterial(pyblish.api.InstancePlugin):
|
||||
class ValidateGLSLMaterial(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""
|
||||
Validate if the asset uses GLSL Shader
|
||||
"""
|
||||
|
|
@ -23,6 +24,8 @@ class ValidateGLSLMaterial(pyblish.api.InstancePlugin):
|
|||
active = True
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
shading_grp = self.get_material_from_shapes(instance)
|
||||
if not shading_grp:
|
||||
raise PublishValidationError("No shading group found")
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import pyblish.api
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateGLSLPlugin(pyblish.api.InstancePlugin):
|
||||
class ValidateGLSLPlugin(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""
|
||||
Validate if the asset uses GLSL Shader
|
||||
"""
|
||||
|
|
@ -19,8 +21,11 @@ class ValidateGLSLPlugin(pyblish.api.InstancePlugin):
|
|||
hosts = ['maya']
|
||||
label = 'maya2glTF plugin'
|
||||
actions = [RepairAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
if not cmds.pluginInfo("maya2glTF", query=True, loaded=True):
|
||||
raise PublishValidationError("maya2glTF is not loaded")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,75 +0,0 @@
|
|||
import maya.cmds as cmds
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.hosts.maya.api import lib
|
||||
from ayon_core.pipeline.publish import PublishValidationError
|
||||
|
||||
|
||||
class ValidateInstancerContent(pyblish.api.InstancePlugin):
|
||||
"""Validates that all meshes in the instance have object IDs.
|
||||
|
||||
This skips a check on intermediate objects because we consider them
|
||||
not important.
|
||||
"""
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = 'Instancer Content'
|
||||
families = ['instancer']
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
error = False
|
||||
members = instance.data['setMembers']
|
||||
export_members = instance.data['exactExportMembers']
|
||||
|
||||
self.log.debug("Contents {0}".format(members))
|
||||
|
||||
if not len(members) == len(cmds.ls(members, type="instancer")):
|
||||
self.log.error("Instancer can only contain instancers")
|
||||
error = True
|
||||
|
||||
# TODO: Implement better check for particles are cached
|
||||
if not cmds.ls(export_members, type="nucleus"):
|
||||
self.log.error("Instancer must have a connected nucleus")
|
||||
error = True
|
||||
|
||||
if not cmds.ls(export_members, type="cacheFile"):
|
||||
self.log.error("Instancer must be cached")
|
||||
error = True
|
||||
|
||||
hidden = self.check_geometry_hidden(export_members)
|
||||
if not hidden:
|
||||
error = True
|
||||
self.log.error("Instancer input geometry must be hidden "
|
||||
"the scene. Invalid: {0}".format(hidden))
|
||||
|
||||
# Ensure all in one group
|
||||
parents = cmds.listRelatives(members,
|
||||
allParents=True,
|
||||
fullPath=True) or []
|
||||
roots = list(set(cmds.ls(parents, assemblies=True, long=True)))
|
||||
if len(roots) > 1:
|
||||
self.log.error("Instancer should all be contained in a single "
|
||||
"group. Current roots: {0}".format(roots))
|
||||
error = True
|
||||
|
||||
if error:
|
||||
raise PublishValidationError(
|
||||
"Instancer Content is invalid. See log.")
|
||||
|
||||
def check_geometry_hidden(self, export_members):
|
||||
|
||||
# Ensure all instanced geometry is hidden
|
||||
shapes = cmds.ls(export_members,
|
||||
dag=True,
|
||||
shapes=True,
|
||||
noIntermediate=True)
|
||||
meshes = cmds.ls(shapes, type="mesh")
|
||||
|
||||
visible = [node for node in meshes
|
||||
if lib.is_visible(node,
|
||||
displayLayer=False,
|
||||
intermediateObject=False)]
|
||||
if visible:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import os
|
||||
import re
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from ayon_core.pipeline.publish import PublishValidationError
|
||||
|
||||
|
||||
def is_cache_resource(resource):
|
||||
"""Return whether resource is a cacheFile resource"""
|
||||
required = set(["maya", "node", "cacheFile"])
|
||||
tags = resource.get("tags", [])
|
||||
return required.issubset(tags)
|
||||
|
||||
|
||||
def valdidate_files(files):
|
||||
for f in files:
|
||||
assert os.path.exists(f)
|
||||
assert f.endswith(".mcx") or f.endswith(".mcc")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def filter_ticks(files):
|
||||
tick_files = set()
|
||||
ticks = set()
|
||||
for path in files:
|
||||
match = re.match(".+Tick([0-9]+).mcx$", os.path.basename(path))
|
||||
if match:
|
||||
tick_files.add(path)
|
||||
num = match.group(1)
|
||||
ticks.add(int(num))
|
||||
|
||||
return tick_files, ticks
|
||||
|
||||
|
||||
class ValidateInstancerFrameRanges(pyblish.api.InstancePlugin):
|
||||
"""Validates all instancer particle systems are cached correctly.
|
||||
|
||||
This means they should have the files/frames as required by the start-end
|
||||
frame (including handles).
|
||||
|
||||
This also checks the files exist and checks the "ticks" (substeps) files.
|
||||
|
||||
"""
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = 'Instancer Cache Frame Ranges'
|
||||
families = ['instancer']
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
import pyseq
|
||||
|
||||
start_frame = instance.data.get("frameStart", 0)
|
||||
end_frame = instance.data.get("frameEnd", 0)
|
||||
required = range(int(start_frame), int(end_frame) + 1)
|
||||
|
||||
invalid = list()
|
||||
resources = instance.data.get("resources", [])
|
||||
|
||||
for resource in resources:
|
||||
if not is_cache_resource(resource):
|
||||
continue
|
||||
|
||||
node = resource['node']
|
||||
all_files = resource['files'][:]
|
||||
all_lookup = set(all_files)
|
||||
|
||||
# The first file is usually the .xml description file.
|
||||
xml = all_files.pop(0)
|
||||
assert xml.endswith(".xml")
|
||||
|
||||
# Ensure all files exist (including ticks)
|
||||
# The remainder file paths should be the .mcx or .mcc files
|
||||
valdidate_files(all_files)
|
||||
|
||||
# Maya particle caches support substeps by saving out additional
|
||||
# files that end with a Tick60.mcx, Tick120.mcx, etc. suffix.
|
||||
# To avoid `pyseq` getting confused we filter those out and then
|
||||
# for each file (except the last frame) check that at least all
|
||||
# ticks exist.
|
||||
|
||||
tick_files, ticks = filter_ticks(all_files)
|
||||
if tick_files:
|
||||
files = [f for f in all_files if f not in tick_files]
|
||||
else:
|
||||
files = all_files
|
||||
|
||||
sequences = pyseq.get_sequences(files)
|
||||
if len(sequences) != 1:
|
||||
invalid.append(node)
|
||||
cls.log.warning("More than one sequence found? "
|
||||
"{0} {1}".format(node, files))
|
||||
cls.log.warning("Found caches: {0}".format(sequences))
|
||||
continue
|
||||
|
||||
sequence = sequences[0]
|
||||
cls.log.debug("Found sequence: {0}".format(sequence))
|
||||
|
||||
start = sequence.start()
|
||||
end = sequence.end()
|
||||
|
||||
if start > start_frame or end < end_frame:
|
||||
invalid.append(node)
|
||||
cls.log.warning("Sequence does not have enough "
|
||||
"frames: {0}-{1} (requires: {2}-{3})"
|
||||
"".format(start, end,
|
||||
start_frame,
|
||||
end_frame))
|
||||
continue
|
||||
|
||||
# Ensure all frames are present
|
||||
missing = set(sequence.missing())
|
||||
if missing:
|
||||
required_missing = [x for x in required if x in missing]
|
||||
if required_missing:
|
||||
invalid.append(node)
|
||||
cls.log.warning("Sequence is missing required frames: "
|
||||
"{0}".format(required_missing))
|
||||
continue
|
||||
|
||||
# Ensure all tick files (substep) exist for the files in the folder
|
||||
# for the frames required by the time range.
|
||||
if ticks:
|
||||
ticks = list(sorted(ticks))
|
||||
cls.log.debug("Found ticks: {0} "
|
||||
"(substeps: {1})".format(ticks, len(ticks)))
|
||||
|
||||
# Check all frames except the last since we don't
|
||||
# require subframes after our time range.
|
||||
tick_check_frames = set(required[:-1])
|
||||
|
||||
# Check all frames
|
||||
for item in sequence:
|
||||
frame = item.frame
|
||||
if not frame:
|
||||
invalid.append(node)
|
||||
cls.log.error("Path is not a frame in sequence: "
|
||||
"{0}".format(item))
|
||||
continue
|
||||
|
||||
# Not required for our time range
|
||||
if frame not in tick_check_frames:
|
||||
continue
|
||||
|
||||
path = item.path
|
||||
for num in ticks:
|
||||
base, ext = os.path.splitext(path)
|
||||
tick_file = base + "Tick{0}".format(num) + ext
|
||||
if tick_file not in all_lookup:
|
||||
invalid.append(node)
|
||||
cls.log.warning("Tick file found that is not "
|
||||
"in cache query filenames: "
|
||||
"{0}".format(tick_file))
|
||||
|
||||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
self.log.error("Invalid nodes: {0}".format(invalid))
|
||||
raise PublishValidationError(
|
||||
("Invalid particle caches in instance. "
|
||||
"See logs for details."))
|
||||
|
|
@ -4,24 +4,27 @@ import maya.cmds as cmds
|
|||
|
||||
from ayon_core.pipeline.publish import (
|
||||
RepairContextAction,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateLoadedPlugin(pyblish.api.ContextPlugin):
|
||||
class ValidateLoadedPlugin(pyblish.api.ContextPlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure there are no unauthorized loaded plugins"""
|
||||
|
||||
label = "Loaded Plugin"
|
||||
order = pyblish.api.ValidatorOrder
|
||||
host = ["maya"]
|
||||
actions = [RepairContextAction]
|
||||
optional = True
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, context):
|
||||
|
||||
invalid = []
|
||||
loaded_plugin = cmds.pluginInfo(query=True, listPlugins=True)
|
||||
# get variable from OpenPype settings
|
||||
# get variable from AYON settings
|
||||
whitelist_native_plugins = cls.whitelist_native_plugins
|
||||
authorized_plugins = cls.authorized_plugins or []
|
||||
|
||||
|
|
@ -35,7 +38,8 @@ class ValidateLoadedPlugin(pyblish.api.ContextPlugin):
|
|||
return invalid
|
||||
|
||||
def process(self, context):
|
||||
|
||||
if not self.is_active(context.data):
|
||||
return
|
||||
invalid = self.get_invalid(context)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import ayon_core.hosts.maya.api.action
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateShadingEngine(pyblish.api.InstancePlugin):
|
||||
class ValidateShadingEngine(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate all shading engines are named after the surface material.
|
||||
|
||||
Shading engines should be named "{surface_shader}SG"
|
||||
|
|
@ -22,9 +24,12 @@ class ValidateShadingEngine(pyblish.api.InstancePlugin):
|
|||
actions = [
|
||||
ayon_core.hosts.maya.api.action.SelectInvalidAction, RepairAction
|
||||
]
|
||||
optional = True
|
||||
|
||||
# The default connections to check
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ from ayon_core.pipeline.context_tools import get_current_project_folder
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairContextAction,
|
||||
ValidateSceneOrder,
|
||||
PublishXmlValidationError
|
||||
PublishXmlValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateMayaUnits(pyblish.api.ContextPlugin):
|
||||
class ValidateMayaUnits(pyblish.api.ContextPlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Check if the Maya units are set correct"""
|
||||
|
||||
order = ValidateSceneOrder
|
||||
|
|
@ -35,6 +37,7 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin):
|
|||
"Maya scene {setting} must be '{required_value}'. "
|
||||
"Current value is '{current_value}'."
|
||||
)
|
||||
optional = False
|
||||
|
||||
@classmethod
|
||||
def apply_settings(cls, project_settings):
|
||||
|
|
@ -52,7 +55,8 @@ class ValidateMayaUnits(pyblish.api.ContextPlugin):
|
|||
cls.validate_fps = settings.get("validate_fps", cls.validate_fps)
|
||||
|
||||
def process(self, context):
|
||||
|
||||
if not self.is_active(context.data):
|
||||
return
|
||||
# Collected units
|
||||
linearunits = context.data.get('linearUnits')
|
||||
angularunits = context.data.get('angularUnits')
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ from maya import cmds
|
|||
|
||||
import pyblish.api
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import ValidateMeshOrder
|
||||
from ayon_core.pipeline.publish import ValidateMeshOrder, OptionalPyblishPluginMixin
|
||||
|
||||
|
||||
class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin):
|
||||
class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate meshes don't have lamina faces.
|
||||
|
||||
Lamina faces share all of their edges.
|
||||
|
|
@ -17,6 +18,7 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin):
|
|||
families = ['model']
|
||||
label = 'Mesh Lamina Faces'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -28,6 +30,8 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance 'objectSet'"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,14 @@ from maya import cmds
|
|||
import pyblish.api
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.hosts.maya.api import lib
|
||||
from ayon_core.pipeline.publish import ValidateContentsOrder
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateMeshNgons(pyblish.api.Validator):
|
||||
class ValidateMeshNgons(pyblish.api.Validator,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure that meshes don't have ngons
|
||||
|
||||
Ngon are faces with more than 4 sides.
|
||||
|
|
@ -21,6 +25,7 @@ class ValidateMeshNgons(pyblish.api.Validator):
|
|||
families = ["model"]
|
||||
label = "Mesh ngons"
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -39,6 +44,8 @@ class ValidateMeshNgons(pyblish.api.Validator):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance "objectSet"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import pyblish.api
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateMeshOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -15,7 +16,8 @@ def _as_report_list(values, prefix="- ", suffix="\n"):
|
|||
return prefix + (suffix + prefix).join(values)
|
||||
|
||||
|
||||
class ValidateMeshNoNegativeScale(pyblish.api.Validator):
|
||||
class ValidateMeshNoNegativeScale(pyblish.api.Validator,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure that meshes don't have a negative scale.
|
||||
|
||||
Using negatively scaled proxies in a VRayMesh results in inverted
|
||||
|
|
@ -32,6 +34,7 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator):
|
|||
families = ['model']
|
||||
label = 'Mesh No Negative Scale'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -52,7 +55,8 @@ class ValidateMeshNoNegativeScale(pyblish.api.Validator):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance 'objectSet'"""
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import pyblish.api
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateMeshOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -15,7 +16,8 @@ def _as_report_list(values, prefix="- ", suffix="\n"):
|
|||
return prefix + (suffix + prefix).join(values)
|
||||
|
||||
|
||||
class ValidateMeshNonManifold(pyblish.api.Validator):
|
||||
class ValidateMeshNonManifold(pyblish.api.Validator,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure that meshes don't have non-manifold edges or vertices
|
||||
|
||||
To debug the problem on the meshes you can use Maya's modeling
|
||||
|
|
@ -28,6 +30,7 @@ class ValidateMeshNonManifold(pyblish.api.Validator):
|
|||
families = ['model']
|
||||
label = 'Mesh Non-Manifold Edges/Vertices'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -44,7 +47,8 @@ class ValidateMeshNonManifold(pyblish.api.Validator):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance 'objectSet'"""
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import ayon_core.hosts.maya.api.action
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateMeshOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -79,7 +80,8 @@ def disconnect(node_a, node_b):
|
|||
cmds.disconnectAttr(source, input)
|
||||
|
||||
|
||||
class ValidateMeshShaderConnections(pyblish.api.InstancePlugin):
|
||||
class ValidateMeshShaderConnections(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure mesh shading engine connections are valid.
|
||||
|
||||
In some scenarios Maya keeps connections to multiple shaders even if just
|
||||
|
|
@ -96,10 +98,12 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin):
|
|||
label = "Mesh Shader Connections"
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction,
|
||||
RepairAction]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance 'objectSet'"""
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ from maya import cmds
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.hosts.maya.api.lib import len_flattened
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError, RepairAction, ValidateMeshOrder)
|
||||
PublishValidationError,
|
||||
RepairAction,
|
||||
ValidateMeshOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
|
||||
class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate meshes have only vertices that are connected to edges.
|
||||
|
||||
Maya can have invalid geometry with vertices that have no edges or
|
||||
|
|
@ -32,6 +37,7 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
|
|||
label = 'Mesh Vertices Have Edges'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction,
|
||||
RepairAction]
|
||||
optional = True
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
|
@ -72,7 +78,8 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin):
|
|||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ import ayon_core.hosts.maya.api.action
|
|||
from ayon_core.hosts.maya.api import lib
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateModelContent(pyblish.api.InstancePlugin):
|
||||
class ValidateModelContent(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Adheres to the content of 'model' product type
|
||||
|
||||
- Must have one top group. (configurable)
|
||||
|
|
@ -24,6 +26,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin):
|
|||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
|
||||
validate_top_group = True
|
||||
optional = False
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -91,7 +94,8 @@ class ValidateModelContent(pyblish.api.InstancePlugin):
|
|||
return list(invalid)
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import pyblish.api
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -15,7 +16,8 @@ def _as_report_list(values, prefix="- ", suffix="\n"):
|
|||
return prefix + (suffix + prefix).join(values)
|
||||
|
||||
|
||||
class ValidateNoDefaultCameras(pyblish.api.InstancePlugin):
|
||||
class ValidateNoDefaultCameras(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure no default (startup) cameras are in the instance.
|
||||
|
||||
This might be unnecessary. In the past there were some issues with
|
||||
|
|
@ -28,6 +30,7 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin):
|
|||
families = ['camera']
|
||||
label = "No Default Cameras"
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -37,6 +40,8 @@ class ValidateNoDefaultCameras(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the cameras in the instance"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@ import pyblish.api
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
import ayon_core.hosts.maya.api.action
|
||||
|
|
@ -24,7 +25,8 @@ def get_namespace(node_name):
|
|||
return node_name.rpartition(":")[0]
|
||||
|
||||
|
||||
class ValidateNoNamespace(pyblish.api.InstancePlugin):
|
||||
class ValidateNoNamespace(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure the nodes don't have a namespace"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
|
|
@ -33,6 +35,7 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin):
|
|||
label = 'No Namespaces'
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction,
|
||||
RepairAction]
|
||||
optional = False
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -41,6 +44,8 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the nodes in the instance"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import ayon_core.hosts.maya.api.action
|
|||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -37,7 +38,8 @@ def has_shape_children(node):
|
|||
return True
|
||||
|
||||
|
||||
class ValidateNoNullTransforms(pyblish.api.InstancePlugin):
|
||||
class ValidateNoNullTransforms(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure no null transforms are in the scene.
|
||||
|
||||
Warning:
|
||||
|
|
@ -54,6 +56,7 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin):
|
|||
label = 'No Empty/Null Transforms'
|
||||
actions = [RepairAction,
|
||||
ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -70,6 +73,8 @@ class ValidateNoNullTransforms(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the transform nodes in the instance """
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import pyblish.api
|
||||
from maya import cmds
|
||||
from ayon_core.pipeline.publish import PublishValidationError
|
||||
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
def _as_report_list(values, prefix="- ", suffix="\n"):
|
||||
"""Return list as bullet point list for a report"""
|
||||
|
|
@ -10,15 +12,18 @@ def _as_report_list(values, prefix="- ", suffix="\n"):
|
|||
return prefix + (suffix + prefix).join(values)
|
||||
|
||||
|
||||
class ValidateNoVRayMesh(pyblish.api.InstancePlugin):
|
||||
class ValidateNoVRayMesh(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate there are no VRayMesh objects in the instance"""
|
||||
|
||||
order = pyblish.api.ValidatorOrder
|
||||
label = 'No V-Ray Proxies (VRayMesh)'
|
||||
families = ["pointcache"]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
if not cmds.pluginInfo("vrayformaya", query=True, loaded=True):
|
||||
return
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@ from maya import cmds
|
|||
import pyblish.api
|
||||
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import ValidateContentsOrder
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
|
||||
|
||||
class ValidateNodeNoGhosting(pyblish.api.InstancePlugin):
|
||||
)
|
||||
class ValidateNodeNoGhosting(pyblish.api.InstancePlugin.
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure nodes do not have ghosting enabled.
|
||||
|
||||
If one would publish towards a non-Maya format it's likely that stats
|
||||
|
|
@ -23,6 +26,7 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin):
|
|||
families = ['model', 'rig']
|
||||
label = "No Ghosting"
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
_attributes = {'ghosting': 0}
|
||||
|
||||
|
|
@ -46,7 +50,8 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin):
|
|||
return invalid
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ from ayon_core.hosts.maya.api.lib import pairwise
|
|||
from ayon_core.hosts.maya.api.action import SelectInvalidAction
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidatePluginPathAttributes(pyblish.api.InstancePlugin):
|
||||
class ValidatePluginPathAttributes(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""
|
||||
Validate plug-in path attributes point to existing file paths.
|
||||
"""
|
||||
|
|
@ -22,6 +24,7 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin):
|
|||
families = ["workfile"]
|
||||
label = "Plug-in Path Attributes"
|
||||
actions = [SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
# Attributes are defined in project settings
|
||||
attribute = []
|
||||
|
|
@ -60,6 +63,8 @@ class ValidatePluginPathAttributes(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all directories Set as Filenames in Non-Maya Nodes"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ import pyblish.api
|
|||
from maya import cmds
|
||||
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError, RepairAction, ValidateContentsOrder)
|
||||
PublishValidationError,
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateRenderImageRule(pyblish.api.InstancePlugin):
|
||||
class ValidateRenderImageRule(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validates Maya Workpace "images" file rule matches project settings.
|
||||
|
||||
This validates against the configured default render image folder:
|
||||
|
|
@ -22,9 +27,11 @@ class ValidateRenderImageRule(pyblish.api.InstancePlugin):
|
|||
hosts = ["maya"]
|
||||
families = ["renderlayer"]
|
||||
actions = [RepairAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
required_images_rule = os.path.normpath(
|
||||
self.get_default_render_image_folder(instance)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import ayon_core.hosts.maya.api.action
|
|||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin):
|
||||
class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure no default (startup) cameras are to be rendered."""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
|
|
@ -17,6 +19,7 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin):
|
|||
families = ['renderlayer']
|
||||
label = "No Default Cameras Renderable"
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
@staticmethod
|
||||
def get_invalid(instance):
|
||||
|
|
@ -32,6 +35,8 @@ class ValidateRenderNoDefaultCameras(pyblish.api.InstancePlugin):
|
|||
|
||||
def process(self, instance):
|
||||
"""Process all the cameras in the instance"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ import ayon_core.hosts.maya.api.action
|
|||
from ayon_core.hosts.maya.api.lib_rendersettings import RenderSettings
|
||||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateRenderSingleCamera(pyblish.api.InstancePlugin):
|
||||
class ValidateRenderSingleCamera(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate renderable camera count for layer and <Camera> token.
|
||||
|
||||
Pipeline is supporting multiple renderable cameras per layer, but image
|
||||
|
|
@ -24,11 +26,14 @@ class ValidateRenderSingleCamera(pyblish.api.InstancePlugin):
|
|||
families = ["renderlayer",
|
||||
"vrayscene"]
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
R_CAMERA_TOKEN = re.compile(r'%c|<camera>', re.IGNORECASE)
|
||||
|
||||
def process(self, instance):
|
||||
"""Process all the cameras in the instance"""
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError("Invalid cameras for render.")
|
||||
|
|
|
|||
|
|
@ -2,10 +2,13 @@ import ayon_api
|
|||
import pyblish.api
|
||||
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import PublishValidationError
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin):
|
||||
class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate created AOVs / RenderElement is registered in the database
|
||||
|
||||
Each render element is registered as a product which is formatted based on
|
||||
|
|
@ -26,8 +29,12 @@ class ValidateRenderLayerAOVs(pyblish.api.InstancePlugin):
|
|||
hosts = ["maya"]
|
||||
families = ["renderlayer"]
|
||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = False
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ from maya import cmds
|
|||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.pipeline.publish import (
|
||||
PublishValidationError,
|
||||
ValidateContentsOrder
|
||||
ValidateContentsOrder,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
|
||||
|
||||
class ValidateRigContents(pyblish.api.InstancePlugin):
|
||||
class ValidateRigContents(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Ensure rig contains pipeline-critical content
|
||||
|
||||
Every rig must contain at least two object sets:
|
||||
|
|
@ -21,11 +23,14 @@ class ValidateRigContents(pyblish.api.InstancePlugin):
|
|||
hosts = ["maya"]
|
||||
families = ["rig"]
|
||||
action = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
optional = True
|
||||
|
||||
accepted_output = ["mesh", "transform"]
|
||||
accepted_controllers = ["transform"]
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
@ -87,9 +92,9 @@ class ValidateRigContents(pyblish.api.InstancePlugin):
|
|||
"""Validate missing objectsets in rig sets
|
||||
|
||||
Args:
|
||||
instance (str): instance
|
||||
required_objsets (list): list of objectset names
|
||||
rig_sets (list): list of rig sets
|
||||
instance (pyblish.api.Instance): instance
|
||||
required_objsets (list[str]): list of objectset names
|
||||
rig_sets (list[str]): list of rig sets
|
||||
|
||||
Raises:
|
||||
PublishValidationError: When the error is raised, it will show
|
||||
|
|
@ -109,15 +114,15 @@ class ValidateRigContents(pyblish.api.InstancePlugin):
|
|||
Check if all rig set members are within the hierarchy of the rig root
|
||||
|
||||
Args:
|
||||
instance (str): instance
|
||||
content (list): list of content from rig sets
|
||||
instance (pyblish.api.Instance): instance
|
||||
content (list[str]): list of content from rig sets
|
||||
|
||||
Raises:
|
||||
PublishValidationError: It means no dag nodes in
|
||||
the rig instance
|
||||
|
||||
Returns:
|
||||
list: invalid hierarchy
|
||||
List[str]: invalid hierarchy
|
||||
"""
|
||||
# Ensure there are at least some transforms or dag nodes
|
||||
# in the rig instance
|
||||
|
|
@ -140,15 +145,13 @@ class ValidateRigContents(pyblish.api.InstancePlugin):
|
|||
|
||||
@classmethod
|
||||
def validate_geometry(cls, set_members):
|
||||
"""
|
||||
Checks if the node types of the set members valid
|
||||
"""Checks if the node types of the set members valid
|
||||
|
||||
Args:
|
||||
set_members: list of nodes of the controls_set
|
||||
hierarchy: list of nodes which reside under the root node
|
||||
set_members (list[str]): nodes of the out_set
|
||||
|
||||
Returns:
|
||||
errors (list)
|
||||
list[str]: Nodes of invalid types.
|
||||
"""
|
||||
|
||||
# Validate all shape types
|
||||
|
|
@ -162,18 +165,17 @@ class ValidateRigContents(pyblish.api.InstancePlugin):
|
|||
if cmds.nodeType(shape) not in cls.accepted_output:
|
||||
invalid.append(shape)
|
||||
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def validate_controls(cls, set_members):
|
||||
"""
|
||||
Checks if the control set members are allowed node types.
|
||||
Checks if the node types of the set members valid
|
||||
"""Checks if the node types of the set members are valid for controls.
|
||||
|
||||
Args:
|
||||
set_members: list of nodes of the controls_set
|
||||
hierarchy: list of nodes which reside under the root node
|
||||
set_members (list[str]): list of nodes of the controls_set
|
||||
|
||||
Returns:
|
||||
errors (list)
|
||||
list: Controls of disallowed node types.
|
||||
"""
|
||||
|
||||
# Validate control types
|
||||
|
|
@ -189,7 +191,7 @@ class ValidateRigContents(pyblish.api.InstancePlugin):
|
|||
"""Get the target objectsets and rig sets nodes
|
||||
|
||||
Args:
|
||||
instance (str): instance
|
||||
instance (pyblish.api.Instance): instance
|
||||
|
||||
Returns:
|
||||
tuple: 2-tuple of list of objectsets,
|
||||
|
|
@ -213,6 +215,7 @@ class ValidateSkeletonRigContents(ValidateRigContents):
|
|||
label = "Skeleton Rig Contents"
|
||||
hosts = ["maya"]
|
||||
families = ["rig.fbx"]
|
||||
optional = True
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
|
@ -247,11 +250,10 @@ class ValidateSkeletonRigContents(ValidateRigContents):
|
|||
"""Get the target objectsets and rig sets nodes
|
||||
|
||||
Args:
|
||||
instance (str): instance
|
||||
instance (pyblish.api.Instance): instance
|
||||
|
||||
Returns:
|
||||
tuple: 2-tuple of list of objectsets,
|
||||
list of rig sets nodes
|
||||
tuple: 2-tuple of list of objectsets, list of rig sets nodes
|
||||
"""
|
||||
objectsets = ["skeletonMesh_SET"]
|
||||
skeleton_mesh_nodes = instance.data.get("skeleton_mesh", [])
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import pyblish.api
|
|||
from ayon_core.pipeline.publish import (
|
||||
ValidateContentsOrder,
|
||||
RepairAction,
|
||||
PublishValidationError
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
import ayon_core.hosts.maya.api.action
|
||||
from ayon_core.hosts.maya.api.lib import undo_chunk
|
||||
|
||||
|
||||
class ValidateRigControllers(pyblish.api.InstancePlugin):
|
||||
class ValidateRigControllers(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validate rig controllers.
|
||||
|
||||
Controls must have the transformation attributes on their default
|
||||
|
|
@ -33,6 +35,7 @@ class ValidateRigControllers(pyblish.api.InstancePlugin):
|
|||
label = "Rig Controllers"
|
||||
hosts = ["maya"]
|
||||
families = ["rig"]
|
||||
optional = True
|
||||
actions = [RepairAction,
|
||||
ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||
|
||||
|
|
@ -50,6 +53,9 @@ class ValidateRigControllers(pyblish.api.InstancePlugin):
|
|||
}
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise PublishValidationError(
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue