Skip to content

extract_last_published

ExtractLastPublished

Bases: 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.

Source code in client/ayon_houdini/plugins/publish/extract_last_published.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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

        expected_filepaths = self.get_expected_files_and_staging_dir(instance)

        # We assume all outputs of this one instance end up in one folder, and
        # hence there being just one 'staging dir'. We will take the first
        # expected file's folder as staging directory.
        staging_dir: str = os.path.dirname(expected_filepaths[0])
        os.makedirs(staging_dir, exist_ok=True)

        expected_and_frames = collect_frames(expected_filepaths)
        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

            # Last published filepath
            file_path = anatomy.fill_root(file_path)
            if not os.path.exists(file_path):
                continue

            # Expected filepath for this render for that frame
            target_filepath: str = frames_and_expected.get(frame)
            if not target_filepath:
                continue

            # Copy only the frames that we won't render.
            if frame in frames_to_fix:
                continue

            self.log.debug(f"Copying '{file_path}' -> '{target_filepath}'")
            shutil.copy(file_path, target_filepath)

    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:
            list[str]: Full paths to the expected filepaths for this publish
                instance.
        """
        expected_filepaths: list[str] = []
        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_filepaths.extend(sum(expected.values(), []))
        else:
            # Products with frames or single file.
            frames = instance.data.get("frames", "")
            staging_dir: str = instance.data.get("stagingDir")
            if isinstance(frames, str):
                # single file.
                expected_filepaths.append("{}/{}".format(staging_dir, frames))
            else:
                # list of frame.
                expected_filepaths.extend(
                    ["{}/{}".format(staging_dir, f) for f in frames]
                )

        return expected_filepaths

get_expected_files_and_staging_dir(instance)

Get expected file names or frames.

This method includes Houdini specific code.

Parameters:

Name Type Description Default
instance Instance

The instance to publish.

required

Returns:

Type Description

list[str]: Full paths to the expected filepaths for this publish instance.

Source code in client/ayon_houdini/plugins/publish/extract_last_published.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
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:
        list[str]: Full paths to the expected filepaths for this publish
            instance.
    """
    expected_filepaths: list[str] = []
    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_filepaths.extend(sum(expected.values(), []))
    else:
        # Products with frames or single file.
        frames = instance.data.get("frames", "")
        staging_dir: str = instance.data.get("stagingDir")
        if isinstance(frames, str):
            # single file.
            expected_filepaths.append("{}/{}".format(staging_dir, frames))
        else:
            # list of frame.
            expected_filepaths.extend(
                ["{}/{}".format(staging_dir, f) for f in frames]
            )

    return expected_filepaths