Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Houdini: Support multiple representations in batch publishing #6

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 91 additions & 6 deletions client/ayon_core/hosts/houdini/plugins/create/create_dynamic.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import os

import re
from ayon_core.pipeline.create import (
Creator,
CreatedInstance,
get_product_name
)
from ayon_api import get_folder_by_path, get_task_by_name
from ayon_core.hosts.houdini.api import lib
import hou


def create_representation_data(files):
Expand All @@ -20,11 +22,75 @@ def create_representation_data(files):
return {
"name": ext,
"ext": ext,
"files": files if len(files) > 1 else first_file,
"files": files if len(files) > 1 else filename,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes - that fixes a bug. Thanks!

"stagingDir": folder,
}


def create_file_list(match, start_frame, end_frame):
"""Collect files based on frame range and `regex.match`

Args:
match(re.match): match object
start_frame(int): start of the animation
end_frame(int): end of the animation

Returns:
list

"""

# Get the padding length
frame = match.group(1)
padding = len(frame)

# Get the parts of the filename surrounding the frame number,
# so we can put our own frame numbers in.
span = match.span(1)
prefix = match.string[: span[0]]
suffix = match.string[span[1]:]

# Generate filenames for all frames
result = []
for i in range(start_frame, end_frame + 1):

# Format frame number by the padding amount
str_frame = "{number:0{width}d}".format(number=i, width=padding)

file_name = prefix + str_frame + suffix
result.append(file_name)

return result


def eval_files_from_output_path(output_path, start_frame=None, end_frame=None):
if start_frame is None:
start_frame = hou.frame()

if end_frame is None:
end_frame = start_frame

output = hou.expandStringAtFrame(output_path, start_frame)
_, ext = lib.splitext(
output, allowed_multidot_extensions=[
".ass.gz", ".bgeo.sc", ".bgeo.gz",
".bgeo.lzma", ".bgeo.bz2"])

result = output
pattern = r"\w+\.(\d+)" + re.escape(ext)
match = re.match(pattern, output)

if match and start_frame is not None:
# Check if frames are bigger than 1 (file collection)
# override the result
if end_frame - start_frame > 0:
result = create_file_list(
match, int(start_frame), int(end_frame)
)

return result


class CreateRuntimeInstance(Creator):
"""Create in-memory instances for dynamic PDG publishing of files.

Expand Down Expand Up @@ -64,11 +130,30 @@ def create(self, product_name, instance_data, pre_create_data):
if custom_instance_data:
instance_data.update(custom_instance_data)

instance_data["families"] = ["dynamic"]

# TODO: Add support for multiple representations
files = pre_create_data["files"]
representations = [create_representation_data(files)]
files = pre_create_data.get("files", [])
if files:
representations = [create_representation_data(files)]

output_paths = pre_create_data.get("output_paths", [])
if output_paths:
Comment on lines +136 to +141
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove one of these two - have a single entry point that is used for the creator which we use as the "API". By exposing more ways we're just adding complexity ;)

By the way, output_paths really got me confused because I see these files actually as the 'input files' for the created instance (since basically this runtime instance expects the files to pre-exist hehe). Anyway, just files or paths or filepaths should suffice.

representations = []
for output_path in output_paths:
files = eval_files_from_output_path(
output_path,
instance_data["frameStart"],
instance_data["frameEnd"])
Comment on lines +144 to +147
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By doing this here - instead of requiring an explicit list of files as input to the creator basically means that this logic with resolving e.g. $F4 which is essentially Houdini-specific now makes this Creator houdini targeted only whereas it would be great that this could work as an even lower-level generic creator ... anywhere.

So basically I do like this - but let's keep thinking about how, by design, this exact same lower level logic can run anywhere without being houdini specific. This "runtime instance" may very well run in a python process on the farm outside of Houdini for example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about passing the representation to the creator.
and in each DCC we should compute it and pass it to the creator.
taking reference from fabia's demo.
image

and then creator can compute frame start and end from the input files.

if isinstance(files, str):
files = [files]
representation = create_representation_data(files)
representation["frameStart"] = instance_data["frameStart"]
representation["frameEnd"] = instance_data["frameEnd"]

representations.append(representation)

instance_data["representations"] = representations
instance_data["families"] = ["dynamic"]

# We ingest it as a different product type then the creator's generic
# ingest product type. For example, we specify `pointcache`
Expand Down Expand Up @@ -133,4 +218,4 @@ def _get_product_name_dynamic(
variant,
dynamic_data=dynamic_data,
project_settings=self.project_settings
)
)
Loading