-
Notifications
You must be signed in to change notification settings - Fork 0
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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): | ||
|
@@ -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, | ||
"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. | ||
|
||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, |
||
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
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` | ||
|
@@ -133,4 +218,4 @@ def _get_product_name_dynamic( | |
variant, | ||
dynamic_data=dynamic_data, | ||
project_settings=self.project_settings | ||
) | ||
) |
There was a problem hiding this comment.
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!