Skip to content

Commit

Permalink
Merge branch 'develop' into enhancement/usd_allow_custom_aov_identifier
Browse files Browse the repository at this point in the history
  • Loading branch information
MustafaJafar authored Dec 19, 2024
2 parents 0ee240d + 373a457 commit f8ece7a
Show file tree
Hide file tree
Showing 15 changed files with 137 additions and 47 deletions.
28 changes: 28 additions & 0 deletions client/ayon_houdini/api/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import re
import logging
import json
import clique
from functools import lru_cache
from contextlib import contextmanager

import six
Expand Down Expand Up @@ -1596,3 +1598,29 @@ def connect_file_parm_to_loader(file_parm: hou.Parm):
f' {file_parm.node().path()} {file_parm.name()} \`{expression}\`'
)
show_node_parmeditor(load_node)


@lru_cache(1)
def is_version_up_workfile_menu_enabled() -> bool:
"""Check if the 'Version Up Workfile' menu should be enabled.
It's cached because we don't care about updating the menu during the
current Houdini session and this allows us to avoid re-querying the
project settings each time.
"""
project_settings = get_current_project_settings()
if project_settings["core"]["tools"]["ayon_menu"].get(
"version_up_current_workfile"
):
return True
return False


def format_as_collections(files: list[str], pattern: str = "{head}{padding}{tail} [{ranges}]") -> list[str]:
"""Return list of files as formatted sequence collections."""

collections, remainder = clique.assemble(files)
result = [collection.format(pattern) for collection in collections]
result.extend(remainder)
return result
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ def process(self, instance):
if errors:
for error in errors:
self.log.error(error)
raise PublishError(f"Failed to save to disk '{node.path()}'")
raise PublishError(
f"Failed to save to disk '{node.path()}'. "
"Please fix your scene to ensure it renders correctly "
"and re-publish. Check the log for more information."
)

# Define the main asset usd file
filepath = node.evalParm("lopoutput")
Expand Down
3 changes: 2 additions & 1 deletion client/ayon_houdini/plugins/publish/collect_output_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def process(self, instance):

else:
raise KnownPublishError(
"ROP node type '{}' is not supported.".format(node_type)
f"ROP node type '{node_type}' is not supported"
f" for product type '{instance.data['product_type']}'"
)

if not out_node:
Expand Down
10 changes: 6 additions & 4 deletions client/ayon_houdini/plugins/publish/collect_render_products.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pyblish.api

from ayon_core.pipeline import PublishError
from ayon_houdini.api import plugin
from ayon_houdini.api.usd import (
get_usd_render_rop_rendersettings
Expand Down Expand Up @@ -108,10 +109,11 @@ def replace(match):
filename = os.path.join(dirname, filename_base)
filename = filename.replace("\\", "/")

assert "#" in filename, (
"Couldn't resolve render product name "
"with frame number: %s" % name
)
if "#" not in filename:
raise PublishError(
"Couldn't resolve render product output file"
f" '{name}' with frame number."
)

filenames.append(filename)

Expand Down
7 changes: 4 additions & 3 deletions client/ayon_houdini/plugins/publish/collect_usd_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pyblish.api

from ayon_core.pipeline import KnownPublishError
from ayon_core.pipeline.create import get_product_name
from ayon_houdini.api import plugin
import ayon_houdini.api.usd as usdlib
Expand All @@ -27,8 +28,7 @@ def copy_instance_data(instance_src, instance_dest, attr):
in the source instance's data.
Raises:
KeyError: If the key does not exist on the source instance.
AssertionError: If a parent key already exists on the destination
KnownPublishError: If a parent key already exists on the destination
instance but is not of the correct type (= is not a dict)
"""
Expand All @@ -43,7 +43,8 @@ def copy_instance_data(instance_src, instance_dest, attr):
src_value = src_data[key]
if i != len(key):
dest_data = dest_data.setdefault(key, {})
assert isinstance(dest_data, dict), "Destination must be a dict"
if not isinstance(dest_data, dict):
raise KnownPublishError("Destination must be a dict.")
src_data = src_value
else:
# Last iteration - assign the value
Expand Down
3 changes: 2 additions & 1 deletion client/ayon_houdini/plugins/publish/extract_hda.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ def process(self, instance):
parm_folder = parm_group.findFolder("Extra")
if not parm_folder:
raise PublishError(
f"Extra parm folder does not exist: {hda_node.path()}"
f"Extra AYON parm folder does not exist on {hda_node.path()}\n\n"
"Please select the node and create an HDA product from the publisher UI."
)

# Remove `Extra` AYON parameters
Expand Down
15 changes: 11 additions & 4 deletions client/ayon_houdini/plugins/publish/extract_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

import pyblish.api

from ayon_core.pipeline import PublishError
from ayon_houdini.api import plugin
from ayon_houdini.api.lib import format_as_collections


class ExtractRender(plugin.HoudiniExtractorPlugin):
Expand Down Expand Up @@ -80,8 +82,13 @@ def process(self, instance):
for frame in all_frames
if not os.path.exists(frame)
]

if missing_frames:
# TODO: Use user friendly error reporting.
raise RuntimeError("Failed to complete render extraction. "
"Missing output files: {}".format(
missing_frames))
# Combine collections for simpler logs of missing files
missing_frames = format_as_collections(missing_frames)
missing_frames = "\n ".join(f"- {sequence}" for sequence in missing_frames)
raise PublishError(
"Failed to complete render extraction.\n"
"Please render any missing output files.",
detail=f"Missing output files: \n {missing_frames}"
)
25 changes: 17 additions & 8 deletions client/ayon_houdini/plugins/publish/extract_rop.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import os
import pyblish.api

from ayon_core.pipeline import publish
from ayon_core.pipeline import publish, PublishError
from ayon_houdini.api import plugin
from ayon_houdini.api.lib import splitext
from ayon_houdini.api.lib import splitext, format_as_collections


class ExtractROP(plugin.HoudiniExtractorPlugin):
Expand Down Expand Up @@ -68,12 +68,21 @@ def validate_expected_frames(self, instance: pyblish.api.Instance):
# Single frame
filenames = [filenames]

missing_filenames = [
filename for filename in filenames
if not os.path.isfile(os.path.join(staging_dir, filename))
]
if missing_filenames:
raise RuntimeError(f"Missing frames: {missing_filenames}")
missing_frames = []
for filename in filenames:
filename = os.path.join(staging_dir, filename)
if not os.path.isfile(filename):
missing_frames.append(filename)

if missing_frames:
# Combine collections for simpler logs of missing files
missing_frames = format_as_collections(missing_frames)
missing_frames = "\n ".join(f"- {sequence}" for sequence in missing_frames)
raise PublishError(
"Failed to complete render extraction.\n"
"Please render any missing output files.",
detail=f"Missing output files: \n {missing_frames}"
)

def update_representation_data(self,
instance: pyblish.api.Instance,
Expand Down
9 changes: 6 additions & 3 deletions client/ayon_houdini/plugins/publish/extract_usd.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pyblish.api

from ayon_core.pipeline import KnownPublishError, PublishError
from ayon_core.pipeline.entity_uri import construct_ayon_entity_uri
from ayon_core.pipeline.publish.lib import get_instance_expected_output_path
from ayon_houdini.api import plugin
Expand Down Expand Up @@ -47,7 +48,8 @@ def process(self, instance):
with remap_paths(ropnode, mapping):
render_rop(ropnode)

assert os.path.exists(output), "Output does not exist: %s" % output
if not os.path.exists(output):
PublishError(f"Output does not exist: {output}")

if "representations" not in instance.data:
instance.data["representations"] = []
Expand Down Expand Up @@ -135,5 +137,6 @@ def get_source_paths(
# Single file
return [os.path.join(staging, files)]

raise TypeError(f"Unsupported type for representation files: {files} "
"(supports list or str)")
raise KnownPublishError(
f"Unsupported type for representation files: {files} (supports list or str)"
)
11 changes: 6 additions & 5 deletions client/ayon_houdini/plugins/publish/increment_current_file.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import pyblish.api

from ayon_core.lib import version_up
from ayon_core.pipeline import registered_host
from ayon_core.pipeline.publish import (
get_errored_plugins_from_context,
from ayon_core.pipeline import (
registered_host,
KnownPublishError
)
from ayon_core.pipeline.publish import get_errored_plugins_from_context

from ayon_houdini.api import plugin

Expand Down Expand Up @@ -42,12 +42,13 @@ def process(self, context):
"submission to deadline failed."
)

# Filename must not have changed since collecting
# Filename must not have changed since collecting.
host = registered_host()
current_file = host.current_file()
if context.data["currentFile"] != current_file:
raise KnownPublishError(
"Collected filename mismatches from current scene name."
f"Collected filename '{context.data['currentFile']}' differs"
f" from current scene name '{current_file}'."
)

new_filepath = version_up(current_file)
Expand Down
22 changes: 17 additions & 5 deletions client/ayon_houdini/plugins/publish/save_scene.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect
import pyblish.api

from ayon_core.pipeline import registered_host
from ayon_core.pipeline import registered_host, PublishError

from ayon_houdini.api import plugin

Expand All @@ -16,12 +17,23 @@ def process(self, context):
# Filename must not have changed since collecting
host = registered_host()
current_file = host.get_current_workfile()
assert context.data['currentFile'] == current_file, (
"Collected filename from current scene name."
)

if context.data['currentFile'] != current_file:
raise PublishError(
f"Collected filename '{context.data['currentFile']}' differs"
f" from current scene name '{current_file}'.",
description=self.get_error_description()
)
if host.workfile_has_unsaved_changes():
self.log.info("Saving current file: {}".format(current_file))
host.save_workfile(current_file)
else:
self.log.debug("No unsaved changes, skipping file save..")


def get_error_description(self):
return inspect.cleandoc(
"""### Scene File Name Changed During Publishing
This error occurs when you validate the scene and then save it as a new file manually, or if you open a new file and continue publishing.
Please reset the publisher and publish without changing the scene file midway.
"""
)
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise PublishValidationError(
("Primitives found with inconsistent primitive "
"to detail attributes. See log."),
title=self.label
"Primitives found with inconsistent primitive "
"to detail attributes.",
detail=(
"See log for more info."
f"Incorrect Rop(s)\n\n - {invalid[0].pah()}"
)
)

@classmethod
Expand Down
3 changes: 2 additions & 1 deletion client/ayon_houdini/plugins/publish/validate_frame_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pyblish.api

from ayon_core.pipeline import PublishValidationError
from ayon_houdini.api import lib, plugin


Expand Down Expand Up @@ -31,7 +32,7 @@ def process(self, instance):

invalid = self.get_invalid(instance)
if invalid:
raise RuntimeError(
raise PublishValidationError(
"Output settings do no match for '%s'" % instance
)

Expand Down
19 changes: 11 additions & 8 deletions client/ayon_houdini/plugins/publish/validate_sop_output_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
raise PublishValidationError(
"Output node(s) are incorrect",
title="Invalid output node(s)"
"Output node(s) are incorrect.",
detail=(
f"Incorrect output SOP path on Rop(s)\n\n - {invalid[0].path()}"
)
)

@classmethod
Expand Down Expand Up @@ -65,9 +67,9 @@ def get_invalid(cls, instance):
# the isinstance check above should be stricter than this category
if output_node.type().category().name() != "Sop":
raise PublishValidationError(
("Output node {} is not of category Sop. "
"This is a bug.").format(output_node.path()),
title=cls.label)
f"Output node {output_node.path()} is not of category Sop.",
title=cls.label
)

# Ensure the node is cooked and succeeds to cook so we can correctly
# check for its geometry data.
Expand All @@ -76,9 +78,10 @@ def get_invalid(cls, instance):
try:
output_node.cook()
except hou.Error as exc:
cls.log.error("Cook failed: %s" % exc)
cls.log.error(output_node.errors()[0])
return [output_node]
raise PublishValidationError(
f"Failed to cook node: {output_node.path()}.",
detail=str(exc)
)

# Ensure the output node has at least Geometry data
if not output_node.geometry():
Expand Down
14 changes: 14 additions & 0 deletions client/ayon_houdini/startup/MainMenuCommon.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ return label

<separatorItem/>

<scriptItem id="ayon_version_up_workfile">
<label>Version Up Workfile</label>
<scriptCode><![CDATA[
from ayon_core.pipeline.context_tools import version_up_current_workfile
version_up_current_workfile()
]]></scriptCode>
<expression>
from ayon_houdini.api.lib import is_version_up_workfile_menu_enabled
return is_version_up_workfile_menu_enabled()
</expression>
</scriptItem>

<separatorItem/>

<scriptItem id="ayon_create">
<label>Create...</label>
<scriptCode><![CDATA[
Expand Down

0 comments on commit f8ece7a

Please sign in to comment.