mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Merge branch 'develop' into enhancement/AY-1228_Load-plugins-update
This commit is contained in:
commit
33f6db2b3e
8 changed files with 245 additions and 95 deletions
|
|
@ -103,19 +103,18 @@ def extractenvironments(output_json_path, project, asset, task, app, envgroup):
|
|||
|
||||
|
||||
@main_cli.command()
|
||||
@click.argument("paths", nargs=-1)
|
||||
@click.option("-t", "--targets", help="Targets module", default=None,
|
||||
@click.argument("path", required=True)
|
||||
@click.option("-t", "--targets", help="Targets", default=None,
|
||||
multiple=True)
|
||||
@click.option("-g", "--gui", is_flag=True,
|
||||
help="Show Publish UI", default=False)
|
||||
def publish(paths, targets, gui):
|
||||
def publish(path, targets, gui):
|
||||
"""Start CLI publishing.
|
||||
|
||||
Publish collects json from paths provided as an argument.
|
||||
More than one path is allowed.
|
||||
Publish collects json from path provided as an argument.
|
||||
S
|
||||
"""
|
||||
|
||||
Commands.publish(list(paths), targets, gui)
|
||||
Commands.publish(path, targets, gui)
|
||||
|
||||
|
||||
@main_cli.command(context_settings={"ignore_unknown_options": True})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import os
|
||||
import sys
|
||||
import json
|
||||
import warnings
|
||||
|
||||
|
||||
class Commands:
|
||||
|
|
@ -41,21 +42,21 @@ class Commands:
|
|||
return click_func
|
||||
|
||||
@staticmethod
|
||||
def publish(paths, targets=None, gui=False):
|
||||
def publish(path: str, targets: list=None, gui:bool=False) -> None:
|
||||
"""Start headless publishing.
|
||||
|
||||
Publish use json from passed paths argument.
|
||||
Publish use json from passed path argument.
|
||||
|
||||
Args:
|
||||
paths (list): Paths to jsons.
|
||||
targets (string): What module should be targeted
|
||||
(to choose validator for example)
|
||||
path (str): Path to JSON.
|
||||
targets (list of str): List of pyblish targets.
|
||||
gui (bool): Show publish UI.
|
||||
|
||||
Raises:
|
||||
RuntimeError: When there is no path to process.
|
||||
"""
|
||||
RuntimeError: When executed with list of JSON paths.
|
||||
|
||||
"""
|
||||
from ayon_core.lib import Logger
|
||||
from ayon_core.lib.applications import (
|
||||
get_app_environments_for_context,
|
||||
|
|
@ -73,6 +74,9 @@ class Commands:
|
|||
import pyblish.api
|
||||
import pyblish.util
|
||||
|
||||
if not isinstance(path, str):
|
||||
raise RuntimeError("Path to JSON must be a string.")
|
||||
|
||||
# Fix older jobs
|
||||
for src_key, dst_key in (
|
||||
("AVALON_PROJECT", "AYON_PROJECT_NAME"),
|
||||
|
|
@ -95,11 +99,8 @@ class Commands:
|
|||
|
||||
publish_paths = manager.collect_plugin_paths()["publish"]
|
||||
|
||||
for path in publish_paths:
|
||||
pyblish.api.register_plugin_path(path)
|
||||
|
||||
if not any(paths):
|
||||
raise RuntimeError("No publish paths specified")
|
||||
for plugin_path in publish_paths:
|
||||
pyblish.api.register_plugin_path(plugin_path)
|
||||
|
||||
app_full_name = os.getenv("AYON_APP_NAME")
|
||||
if app_full_name:
|
||||
|
|
@ -122,7 +123,7 @@ class Commands:
|
|||
else:
|
||||
pyblish.api.register_target("farm")
|
||||
|
||||
os.environ["AYON_PUBLISH_DATA"] = os.pathsep.join(paths)
|
||||
os.environ["AYON_PUBLISH_DATA"] = path
|
||||
os.environ["HEADLESS_PUBLISH"] = 'true' # to use in app lib
|
||||
|
||||
log.info("Running publish ...")
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
from pymxs import runtime as rt
|
||||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
|
||||
|
||||
|
||||
class ValidateDeadlinePublish(pyblish.api.InstancePlugin,
|
||||
OptionalPyblishPluginMixin):
|
||||
"""Validates Render File Directory is
|
||||
not the same in every submission
|
||||
"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
families = ["maxrender"]
|
||||
hosts = ["max"]
|
||||
label = "Render Output for Deadline"
|
||||
optional = True
|
||||
actions = [RepairAction]
|
||||
|
||||
def process(self, instance):
|
||||
if not self.is_active(instance.data):
|
||||
return
|
||||
file = rt.maxFileName
|
||||
filename, ext = os.path.splitext(file)
|
||||
if filename not in rt.rendOutputFilename:
|
||||
raise PublishValidationError(
|
||||
"Render output folder "
|
||||
"doesn't match the max scene name! "
|
||||
"Use Repair action to "
|
||||
"fix the folder file path.."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
container = instance.data.get("instance_node")
|
||||
RenderSettings().render_output(container)
|
||||
cls.log.debug("Reset the render output folder...")
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
import os
|
||||
import pyblish.api
|
||||
from pymxs import runtime as rt
|
||||
from ayon_core.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError,
|
||||
OptionalPyblishPluginMixin
|
||||
)
|
||||
from ayon_core.hosts.max.api.lib_rendersettings import RenderSettings
|
||||
|
||||
|
||||
class ValidateRenderPasses(OptionalPyblishPluginMixin,
|
||||
pyblish.api.InstancePlugin):
|
||||
"""Validates Render Passes before farm submission
|
||||
"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
families = ["maxrender"]
|
||||
hosts = ["max"]
|
||||
label = "Validate Render Passes"
|
||||
actions = [RepairAction]
|
||||
|
||||
def process(self, instance):
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
bullet_point_invalid_statement = "\n".join(
|
||||
f"- {err_type}: {filepath}" for err_type, filepath
|
||||
in invalid
|
||||
)
|
||||
report = (
|
||||
"Invalid render passes found.\n\n"
|
||||
f"{bullet_point_invalid_statement}\n\n"
|
||||
"You can use repair action to fix the invalid filepath."
|
||||
)
|
||||
raise PublishValidationError(
|
||||
report, title="Invalid Render Passes")
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
"""Function to get invalid beauty render outputs and
|
||||
render elements.
|
||||
|
||||
1. Check Render Output Folder matches the name of
|
||||
the current Max Scene, e.g.
|
||||
The name of the current Max scene:
|
||||
John_Doe.max
|
||||
The expected render output directory:
|
||||
{root[work]}/{project[name]}/{hierarchy}/{asset}/
|
||||
work/{task[name]}/render/3dsmax/John_Doe/
|
||||
|
||||
2. Check image extension(s) of the render output(s)
|
||||
matches the image format in OP/AYON setting, e.g.
|
||||
The current image format in settings: png
|
||||
The expected render outputs: John_Doe.png
|
||||
|
||||
3. Check filename of render element ends with the name of
|
||||
render element from the 3dsMax Render Element Manager.
|
||||
e.g. The name of render element: RsCryptomatte
|
||||
The expected filename: {InstanceName}_RsCryptomatte.png
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): instance
|
||||
workfile_name (str): filename of the Max scene
|
||||
|
||||
Returns:
|
||||
list: list of invalid filename which doesn't match
|
||||
with the project name
|
||||
"""
|
||||
invalid = []
|
||||
file = rt.maxFileName
|
||||
workfile_name, ext = os.path.splitext(file)
|
||||
if workfile_name not in rt.rendOutputFilename:
|
||||
cls.log.error(
|
||||
"Render output folder must include"
|
||||
f" the max scene name {workfile_name} "
|
||||
)
|
||||
invalid_folder_name = os.path.dirname(
|
||||
rt.rendOutputFilename).replace(
|
||||
"\\", "/").split("/")[-1]
|
||||
invalid.append(("Invalid Render Output Folder",
|
||||
invalid_folder_name))
|
||||
beauty_fname = os.path.basename(rt.rendOutputFilename)
|
||||
beauty_name, ext = os.path.splitext(beauty_fname)
|
||||
invalid_filenames = cls.get_invalid_filenames(
|
||||
instance, beauty_name)
|
||||
invalid.extend(invalid_filenames)
|
||||
invalid_image_format = cls.get_invalid_image_format(
|
||||
instance, ext.lstrip("."))
|
||||
invalid.extend(invalid_image_format)
|
||||
renderer = instance.data["renderer"]
|
||||
if renderer in [
|
||||
"ART_Renderer",
|
||||
"Redshift_Renderer",
|
||||
"V_Ray_6_Hotfix_3",
|
||||
"V_Ray_GPU_6_Hotfix_3",
|
||||
"Default_Scanline_Renderer",
|
||||
"Quicksilver_Hardware_Renderer",
|
||||
]:
|
||||
render_elem = rt.maxOps.GetCurRenderElementMgr()
|
||||
render_elem_num = render_elem.NumRenderElements()
|
||||
for i in range(render_elem_num):
|
||||
renderlayer_name = render_elem.GetRenderElement(i)
|
||||
renderpass = str(renderlayer_name).rsplit(":", 1)[-1]
|
||||
rend_file = render_elem.GetRenderElementFilename(i)
|
||||
if not rend_file:
|
||||
continue
|
||||
|
||||
rend_fname, ext = os.path.splitext(
|
||||
os.path.basename(rend_file))
|
||||
invalid_filenames = cls.get_invalid_filenames(
|
||||
instance, rend_fname, renderpass=renderpass)
|
||||
invalid.extend(invalid_filenames)
|
||||
invalid_image_format = cls.get_invalid_image_format(
|
||||
instance, ext)
|
||||
invalid.extend(invalid_image_format)
|
||||
elif renderer == "Arnold":
|
||||
cls.log.debug(
|
||||
"Renderpass validation does not support Arnold yet,"
|
||||
" validation skipped...")
|
||||
else:
|
||||
cls.log.debug(
|
||||
"Skipping render element validation "
|
||||
f"for renderer: {renderer}")
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def get_invalid_filenames(cls, instance, file_name, renderpass=None):
|
||||
"""Function to get invalid filenames from render outputs.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): instance
|
||||
file_name (str): name of the file
|
||||
renderpass (str, optional): name of the renderpass.
|
||||
Defaults to None.
|
||||
|
||||
Returns:
|
||||
list: invalid filenames
|
||||
"""
|
||||
invalid = []
|
||||
if instance.name not in file_name:
|
||||
cls.log.error("The renderpass filename should contain the instance name.")
|
||||
invalid.append((f"Invalid instance name",
|
||||
file_name))
|
||||
if renderpass is not None:
|
||||
if not file_name.rstrip(".").endswith(renderpass):
|
||||
cls.log.error(
|
||||
f"Filename for {renderpass} should "
|
||||
f"end with {renderpass}: {file_name}"
|
||||
)
|
||||
invalid.append((f"Invalid {renderpass}",
|
||||
os.path.basename(file_name)))
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def get_invalid_image_format(cls, instance, ext):
|
||||
"""Function to check if the image format of the render outputs
|
||||
aligns with that in the setting.
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): instance
|
||||
ext (str): image extension
|
||||
|
||||
Returns:
|
||||
list: list of files with invalid image format
|
||||
"""
|
||||
invalid = []
|
||||
settings = instance.context.data["project_settings"].get("max")
|
||||
image_format = settings["RenderSettings"]["image_format"]
|
||||
ext = ext.lstrip(".")
|
||||
if ext != image_format:
|
||||
msg = (
|
||||
f"Invalid image format {ext} for render outputs.\n"
|
||||
f"Should be: {image_format}")
|
||||
cls.log.error(msg)
|
||||
invalid.append((msg, ext))
|
||||
return invalid
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
container = instance.data.get("instance_node")
|
||||
# TODO: need to rename the function of render_output
|
||||
RenderSettings().render_output(container)
|
||||
cls.log.debug("Finished repairing the render output "
|
||||
"folder and filenames.")
|
||||
|
|
@ -608,7 +608,7 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase):
|
|||
return get_product_name(
|
||||
project_name,
|
||||
task_name,
|
||||
task_type
|
||||
task_type,
|
||||
host_name,
|
||||
self.layer_instance_prefix or self.product_type,
|
||||
variant,
|
||||
|
|
|
|||
|
|
@ -330,19 +330,25 @@ def get_timeline_item(media_pool_item: object,
|
|||
Returns:
|
||||
object: resolve.TimelineItem
|
||||
"""
|
||||
_clip_property = media_pool_item.GetClipProperty
|
||||
clip_name = _clip_property("File Name")
|
||||
clip_name = media_pool_item.GetClipProperty("File Name")
|
||||
output_timeline_item = None
|
||||
timeline = timeline or get_current_timeline()
|
||||
|
||||
with maintain_current_timeline(timeline):
|
||||
# search the timeline for the added clip
|
||||
|
||||
for _ti_data in get_current_timeline_items():
|
||||
_ti_clip = _ti_data["clip"]["item"]
|
||||
_ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty
|
||||
if clip_name in _ti_clip_property("File Name"):
|
||||
output_timeline_item = _ti_clip
|
||||
for ti_data in get_current_timeline_items():
|
||||
ti_clip_item = ti_data["clip"]["item"]
|
||||
ti_media_pool_item = ti_clip_item.GetMediaPoolItem()
|
||||
|
||||
# Skip items that do not have a media pool item, like for example
|
||||
# an "Adjustment Clip" or a "Fusion Composition" from the effects
|
||||
# toolbox
|
||||
if not ti_media_pool_item:
|
||||
continue
|
||||
|
||||
if clip_name in ti_media_pool_item.GetClipProperty("File Name"):
|
||||
output_timeline_item = ti_clip_item
|
||||
|
||||
return output_timeline_item
|
||||
|
||||
|
|
|
|||
|
|
@ -36,18 +36,18 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
|
||||
def _load_json(self, path):
|
||||
path = path.strip('\"')
|
||||
assert os.path.isfile(path), (
|
||||
"Path to json file doesn't exist. \"{}\"".format(path)
|
||||
)
|
||||
|
||||
if not os.path.isfile(path):
|
||||
raise FileNotFoundError(
|
||||
f"Path to json file doesn't exist. \"{path}\"")
|
||||
|
||||
data = None
|
||||
with open(path, "r") as json_file:
|
||||
try:
|
||||
data = json.load(json_file)
|
||||
except Exception as exc:
|
||||
self.log.error(
|
||||
"Error loading json: "
|
||||
"{} - Exception: {}".format(path, exc)
|
||||
)
|
||||
"Error loading json: %s - Exception: %s", path, exc)
|
||||
return data
|
||||
|
||||
def _fill_staging_dir(self, data_object, anatomy):
|
||||
|
|
@ -73,30 +73,23 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
|
|||
data_err = "invalid json file - missing data"
|
||||
required = ["user", "comment",
|
||||
"job", "instances", "version"]
|
||||
assert all(elem in data.keys() for elem in required), data_err
|
||||
|
||||
if any(elem not in data for elem in required):
|
||||
raise ValueError(data_err)
|
||||
|
||||
if "folderPath" not in data and "asset" not in data:
|
||||
raise AssertionError(data_err)
|
||||
raise ValueError(data_err)
|
||||
|
||||
if "folderPath" not in data:
|
||||
data["folderPath"] = data.pop("asset")
|
||||
|
||||
# set context by first json file
|
||||
ctx = self._context.data
|
||||
|
||||
ctx["folderPath"] = ctx.get("folderPath") or data.get("folderPath")
|
||||
ctx["intent"] = ctx.get("intent") or data.get("intent")
|
||||
ctx["comment"] = ctx.get("comment") or data.get("comment")
|
||||
ctx["user"] = ctx.get("user") or data.get("user")
|
||||
ctx["version"] = ctx.get("version") or data.get("version")
|
||||
|
||||
# basic sanity check to see if we are working in same context
|
||||
# if some other json file has different context, bail out.
|
||||
ctx_err = "inconsistent contexts in json files - %s"
|
||||
assert ctx.get("folderPath") == data.get("folderPath"), ctx_err % "folderPath"
|
||||
assert ctx.get("intent") == data.get("intent"), ctx_err % "intent"
|
||||
assert ctx.get("comment") == data.get("comment"), ctx_err % "comment"
|
||||
assert ctx.get("user") == data.get("user"), ctx_err % "user"
|
||||
assert ctx.get("version") == data.get("version"), ctx_err % "version"
|
||||
# ftrack credentials are passed as environment variables by Deadline
|
||||
# to publish job, but Muster doesn't pass them.
|
||||
if data.get("ftrack") and not os.environ.get("FTRACK_API_USER"):
|
||||
ftrack = data.get("ftrack")
|
||||
os.environ["FTRACK_API_USER"] = ftrack["FTRACK_API_USER"]
|
||||
os.environ["FTRACK_API_KEY"] = ftrack["FTRACK_API_KEY"]
|
||||
os.environ["FTRACK_SERVER"] = ftrack["FTRACK_SERVER"]
|
||||
|
||||
# now we can just add instances from json file and we are done
|
||||
any_staging_dir_persistent = False
|
||||
|
|
|
|||
|
|
@ -116,6 +116,10 @@ class PublishersModel(BaseSettingsModel):
|
|||
default_factory=ValidateModelNameModel,
|
||||
title="Validate Model Name"
|
||||
)
|
||||
ValidateRenderPasses: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Validate Render Passes"
|
||||
)
|
||||
ExtractModelObj: BasicValidateModel = SettingsField(
|
||||
default_factory=BasicValidateModel,
|
||||
title="Extract OBJ",
|
||||
|
|
@ -185,6 +189,11 @@ DEFAULT_PUBLISH_SETTINGS = {
|
|||
"optional": True,
|
||||
"active": False,
|
||||
},
|
||||
"ValidateRenderPasses": {
|
||||
"enabled": True,
|
||||
"optional": False,
|
||||
"active": True
|
||||
},
|
||||
"ExtractModelObj": {
|
||||
"enabled": True,
|
||||
"optional": True,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue