Merge branch 'develop' into chore/add_zbrush_as_application

This commit is contained in:
Kayla Man 2024-03-26 09:52:46 +00:00 committed by GitHub
commit aa6a9800a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
194 changed files with 3327 additions and 2794 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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}`")

View file

@ -1,5 +1,5 @@
"""
OpenPype Autodesk Flame api
AYON Autodesk Flame api
"""
from .constants import (
COLOR_MAP,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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",

View file

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

View file

@ -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",
]

View file

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

View file

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

View file

@ -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"]

View file

@ -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`.

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = []

View file

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

View file

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

View file

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

View file

@ -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": {

View file

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

View file

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

View file

@ -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?"

View file

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

View file

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

View file

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

View file

@ -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."""

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
-- OpenPype Init Script
-- AYON Init Script
(
local sysPath = dotNetClass "System.IO.Path"
local sysDir = dotNetClass "System.IO.Directory"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = {

View file

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

View 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:

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = []

View file

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

View file

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

View file

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

View file

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

View file

@ -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: "

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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", [])

View file

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