generated from ynput/ayon-addon-template
-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #45 from ynput/enhancement/AY-1014_Houdini-Overwri…
…te-the-last-publish_houdini-twin Overwrite the last publish
- Loading branch information
Showing
9 changed files
with
452 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
143 changes: 143 additions & 0 deletions
143
client/ayon_houdini/plugins/publish/collect_frames_fix.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import pyblish.api | ||
import ayon_api | ||
|
||
from ayon_core.lib.attribute_definitions import ( | ||
TextDef, | ||
BoolDef | ||
) | ||
from ayon_core.pipeline.publish import AYONPyblishPluginMixin | ||
|
||
from ayon_houdini.api import plugin | ||
|
||
|
||
class CollectFramesFixDefHou( | ||
plugin.HoudiniInstancePlugin, | ||
AYONPyblishPluginMixin | ||
): | ||
"""Provides text field to insert frame(s) to be re-rendered. | ||
Published files of last version of an instance product are collected into | ||
`instance.data["last_version_published_files"]`. All these but frames | ||
mentioned in text field will be reused for new version. | ||
""" | ||
order = pyblish.api.CollectorOrder + 0.495 | ||
label = "Collect Frames to Fix" | ||
targets = ["local"] | ||
families = ["*"] | ||
|
||
rewrite_version_enable = False | ||
|
||
def process(self, instance): | ||
attribute_values = self.get_attr_values_from_data(instance.data) | ||
frames_to_fix: str = attribute_values.get("frames_to_fix", "") | ||
rewrite_version: bool = ( | ||
self.rewrite_version_enable | ||
and attribute_values.get("rewrite_version", False) | ||
) | ||
if not frames_to_fix: | ||
if rewrite_version: | ||
self.log.warning( | ||
"Rewrite version is enabled but no frames to fix are " | ||
"specified. Rewriting last version will be skipped.") | ||
return | ||
|
||
self.log.info(f"Frames to fix: {frames_to_fix}") | ||
instance.data["frames_to_fix"] = frames_to_fix | ||
|
||
# Skip instances that are set to not be integrated so we ignore | ||
# the original `render` instance from which local AOV instances are | ||
# spawned off. | ||
if not instance.data.get("integrate", True): | ||
self.log.debug("Skipping collecting frames to fix data for " | ||
"instance because instance is set to not integrate") | ||
return | ||
|
||
product_name: str = instance.data["productName"] | ||
folder_entity: dict = instance.data["folderEntity"] | ||
project_entity: dict = instance.data["projectEntity"] | ||
project_name: str = project_entity["name"] | ||
|
||
product_entity = ayon_api.get_product_by_name( | ||
project_name, | ||
product_name, | ||
folder_id=folder_entity["id"]) | ||
if not product_entity: | ||
self.log.warning( | ||
f"No existing product found for '{product_name}'. " | ||
"Re-render not possible." | ||
) | ||
return | ||
|
||
product_type = product_entity["productType"] | ||
instance_product_type = instance.data["productType"] | ||
if product_type != instance_product_type: | ||
self.log.error( | ||
f"Existing product '{product_name}' product type " | ||
f"'{product_type}' is not the same as instance product type " | ||
f"'{instance_product_type}'. Re-render may have unintended " | ||
f"side effects.") | ||
|
||
version_entity = ayon_api.get_last_version_by_product_id( | ||
project_name, | ||
product_id=product_entity["id"], | ||
) | ||
if not version_entity: | ||
self.log.warning( | ||
f"No last version found for product '{product_name}', " | ||
"re-render not possible." | ||
) | ||
return | ||
|
||
representations = ayon_api.get_representations( | ||
project_name, version_ids={version_entity["id"]} | ||
) | ||
|
||
# Get all published files for the representation | ||
published_files: "list[str]" = [] | ||
for repre in representations: | ||
for file_info in repre.get("files"): | ||
published_files.append(file_info["path"]) | ||
|
||
instance.data["last_version_published_files"] = published_files | ||
self.log.debug(f"last_version_published_files: {published_files}") | ||
|
||
if rewrite_version: | ||
instance.data["version"] = version_entity["version"] | ||
# limits triggering version validator | ||
instance.data.pop("latestVersion") | ||
|
||
@classmethod | ||
def get_attribute_defs(cls): | ||
attributes = [ | ||
TextDef("frames_to_fix", label="Frames to fix", | ||
placeholder="5,10-15", | ||
regex="[0-9,-]+", | ||
tooltip=( | ||
"When specified, only these frames will be rendered.\n" | ||
"The remainder of the frame range for the instance " | ||
"will be copied from the previous published version.\n" | ||
"This allows re-rendering only certain frames or " | ||
"extending the frame range of the previous version.\n" | ||
"The frames to fix must be inside the instance's " | ||
"frame range.\n" | ||
"Example: 5,10-15" | ||
)) | ||
] | ||
|
||
if cls.rewrite_version_enable: | ||
attributes.append( | ||
BoolDef( | ||
"rewrite_version", | ||
label="Rewrite latest version", | ||
default=False, | ||
tooltip=( | ||
"When enabled the new version will be published into" | ||
"the previous version and apply only the 'fixed " | ||
"frames'.\n" | ||
"**Note:** This does nothing if no Frames to Fix are " | ||
"specified." | ||
) | ||
) | ||
) | ||
|
||
return attributes |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
client/ayon_houdini/plugins/publish/extract_last_published.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import os | ||
import shutil | ||
|
||
import clique | ||
import pyblish.api | ||
|
||
from ayon_core.lib import collect_frames | ||
from ayon_houdini.api import plugin | ||
|
||
|
||
class ExtractLastPublished(plugin.HoudiniExtractorPlugin): | ||
"""Extractor copying files from last published to staging directory. | ||
It works only if instance data includes "last_version_published_files" | ||
and there are frames to fix. | ||
The files from last published are based on files which will be | ||
extended/fixed for specific frames. | ||
NOTE: | ||
This plugin is closely taken from ayon-nuke. | ||
It contains some Houdini addon specific logic as various addons may | ||
have unique methods for managing `staging_dir`, `expectedFiles` | ||
and `frames`. | ||
TODO: | ||
It's preferable to to generalize this plugin for broader use and | ||
integrate it into ayon-core. | ||
""" | ||
|
||
order = pyblish.api.ExtractorOrder - 0.1 | ||
label = "Extract Last Published" | ||
targets = ["local"] # Same target as `CollectFramesFixDef` | ||
families = ["*"] | ||
|
||
def process(self, instance): | ||
frames_to_fix = instance.data.get("frames_to_fix") | ||
if not frames_to_fix: | ||
self.log.debug("Skipping, No frames to fix.") | ||
return | ||
|
||
if not instance.data.get("integrate", True): | ||
self.log.debug("Skipping collecting frames to fix data for " | ||
"instance because instance is set to not integrate") | ||
return | ||
|
||
last_published = instance.data.get("last_version_published_files") | ||
if not last_published: | ||
self.log.debug("Skipping, No last publish found.") | ||
return | ||
|
||
last_published_and_frames = collect_frames(last_published) | ||
if not all(last_published_and_frames.values()): | ||
self.log.debug("Skipping, No file sequence found in the " | ||
"last version published files.") | ||
return | ||
|
||
staging_dir, expected_filenames = self.get_expected_files_and_staging_dir(instance) | ||
|
||
os.makedirs(staging_dir, exist_ok=True) | ||
|
||
expected_and_frames = collect_frames(expected_filenames) | ||
frames_and_expected = {v: k for k, v in expected_and_frames.items()} | ||
frames_to_fix = clique.parse(frames_to_fix, "{ranges}") | ||
|
||
anatomy = instance.context.data["anatomy"] | ||
|
||
# TODO: This currently copies ALL frames from the last version instead | ||
# of only those within the frame range we're currently looking to | ||
# publish. It should instead, iterate over all expected frames for | ||
# current instance, exclude all "to fix" frames and copy the | ||
# other existing ones. | ||
for file_path, frame in last_published_and_frames.items(): | ||
if frame is None: | ||
continue | ||
file_path = anatomy.fill_root(file_path) | ||
if not os.path.exists(file_path): | ||
continue | ||
target_file_name = frames_and_expected.get(frame) | ||
if not target_file_name: | ||
continue | ||
|
||
out_path = os.path.join(staging_dir, target_file_name) | ||
|
||
# Copy only the frames that we won't render. | ||
if frame and frame not in frames_to_fix: | ||
self.log.debug(f"Copying '{file_path}' -> '{out_path}'") | ||
shutil.copy(file_path, out_path) | ||
|
||
def get_expected_files_and_staging_dir(self, instance): | ||
"""Get expected file names or frames. | ||
This method includes Houdini specific code. | ||
Args: | ||
instance (pyblish.api.Instance): The instance to publish. | ||
Returns: | ||
tuple[str, list[str]]: A 2-tuple of staging dir and the list of | ||
expected frames for the current publish instance. | ||
""" | ||
expected_filenames = [] | ||
staging_dir = instance.data.get("stagingDir") | ||
expected_files = instance.data.get("expectedFiles", []) | ||
|
||
# 'expectedFiles' are preferred over 'frames' | ||
if expected_files: | ||
# Products with expected files | ||
# This can be Render products or submitted cache to farm. | ||
for expected in expected_files: | ||
# expected.values() is a list of lists | ||
expected_filenames.extend(sum(expected.values(), [])) | ||
else: | ||
# Products with frames or single file. | ||
frames = instance.data.get("frames", "") | ||
if isinstance(frames, str): | ||
# single file. | ||
expected_filenames.append("{}/{}".format(staging_dir, frames)) | ||
else: | ||
# list of frame. | ||
expected_filenames.extend( | ||
["{}/{}".format(staging_dir, f) for f in frames] | ||
) | ||
|
||
return staging_dir, expected_filenames |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.