Skip to content

integrate_usd_output_processor_remap_relative

USDOutputProcessorRemapToRelativePaths

Bases: InstancePlugin, OptionalPyblishPluginMixin

Remap all paths in a USD Layer to be relative to its published path

Source code in client/ayon_usd/plugins/publish/integrate_usd_output_processor_remap_relative.py
 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
133
134
135
136
137
138
139
140
class USDOutputProcessorRemapToRelativePaths(pyblish.api.InstancePlugin,
                                             OptionalPyblishPluginMixin):
    """Remap all paths in a USD Layer to be relative to its published path"""

    label = "Process USD files to use relative paths"
    families = ["usd"]
    settings_category = "usd"

    # Run just before the Integrator
    order = pyblish.api.IntegratorOrder - 0.01

    def process(self, instance):
        if not self.is_active(instance.data):
            return

        # Skip instance if not marked for integration
        if not instance.data.get("integrate", True):
            return

        # Some hosts may not have USD libs available but can publish USD data.
        # For those we'll log a warning.
        if not HAS_USD_LIBS:
            self.log.warning(
                "Unable to process USD files to relative paths because "
                "`pxr` USD libraries could not be imported.")
            return

        # For each USD representation, process the file.
        for representation in instance.data.get("representations", []):
            representation: dict

            if representation.get("name") != "usd":
                continue

            # Get expected publish path
            published_path = get_instance_expected_output_path(
                instance,
                representation_name=representation["name"],
                ext=representation.get("ext")
            )
            published_path_root = os.path.dirname(published_path)
            self.log.debug(
                f"Making USD paths relative to {published_path_root}")

            # Process all files of the representation
            staging_dir: str = representation.get(
                "stagingDir", instance.data.get("stagingDir"))

            # Process single file or sequence of the representation
            filenames = representation["files"]
            if isinstance(filenames, str):
                # Single file is stored as `str` in `instance.data["files"]`
                filenames = [filenames]

            filenames: "list[str]"
            for filename in filenames:
                path = os.path.join(staging_dir, filename)
                self.process_usd(path, start=published_path_root)

        # Some instance may have additional transferred files which
        # themselves are not a representation. For those we need to look in
        # the `instance.data["transfers"]`
        for src, dest in instance.data.get("transfers", []):
            if not dest.endswith(".usd"):
                continue

            # Process USD file at `src` and turn all paths relative to
            # the `dest` path the file will end up at.
            dest_root = os.path.dirname(dest)
            self.process_usd(src, start=dest_root)

    def process_usd(self, usd_path, start):
        """Process a USD layer making all paths relative to `start`"""
        self.log.debug(f"Processing '{usd_path}'")
        layer = Sdf.Layer.FindOrOpen(usd_path)

        def modify_fn(asset_path: str):
            """Make all absolute non-anchored paths relative to `start`"""
            self.log.debug(f"Processing asset path: {asset_path}")

            # Do not touch paths already anchored paths
            if not os.path.isabs(asset_path):
                return asset_path

            # Do not touch paths already anchored paths
            if asset_path.startswith(RELATIVE_ANCHOR_PREFIXES):
                # Already anchored
                return asset_path

            # Do not touch what we know are AYON URIs
            if asset_path.startswith(("ayon://", "ayon+entity://")):
                return asset_path

            # Consider only files on the same drive, because otherwise no
            # 'relative' path exists for the file.
            if get_drive(start) != get_drive(asset_path):
                # Log a warning if different drive
                self.log.warning(
                    f"USD Asset Path '{asset_path}' can not be made relative"
                    f" to '{start}' because they are not on the same drive.")
                return asset_path

            anchored_path = "./" + os.path.relpath(asset_path, start)

            # Force forward slashes so that relative paths generated on Windows
            # load correctly on e.g. Linux. It also makes the paths consistent
            # regardless of platforms
            anchored_path = anchored_path.replace("\\", "/")
            self.log.debug(f"Anchored path: {anchored_path}")
            return anchored_path

        # Get all "asset path" specs, sublayer paths and references/payloads.
        # Make all the paths relative.
        UsdUtils.ModifyAssetPaths(layer, modify_fn)
        if layer.dirty:
            layer.Save()

process_usd(usd_path, start)

Process a USD layer making all paths relative to start

Source code in client/ayon_usd/plugins/publish/integrate_usd_output_processor_remap_relative.py
 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
133
134
135
136
137
138
139
140
def process_usd(self, usd_path, start):
    """Process a USD layer making all paths relative to `start`"""
    self.log.debug(f"Processing '{usd_path}'")
    layer = Sdf.Layer.FindOrOpen(usd_path)

    def modify_fn(asset_path: str):
        """Make all absolute non-anchored paths relative to `start`"""
        self.log.debug(f"Processing asset path: {asset_path}")

        # Do not touch paths already anchored paths
        if not os.path.isabs(asset_path):
            return asset_path

        # Do not touch paths already anchored paths
        if asset_path.startswith(RELATIVE_ANCHOR_PREFIXES):
            # Already anchored
            return asset_path

        # Do not touch what we know are AYON URIs
        if asset_path.startswith(("ayon://", "ayon+entity://")):
            return asset_path

        # Consider only files on the same drive, because otherwise no
        # 'relative' path exists for the file.
        if get_drive(start) != get_drive(asset_path):
            # Log a warning if different drive
            self.log.warning(
                f"USD Asset Path '{asset_path}' can not be made relative"
                f" to '{start}' because they are not on the same drive.")
            return asset_path

        anchored_path = "./" + os.path.relpath(asset_path, start)

        # Force forward slashes so that relative paths generated on Windows
        # load correctly on e.g. Linux. It also makes the paths consistent
        # regardless of platforms
        anchored_path = anchored_path.replace("\\", "/")
        self.log.debug(f"Anchored path: {anchored_path}")
        return anchored_path

    # Get all "asset path" specs, sublayer paths and references/payloads.
    # Make all the paths relative.
    UsdUtils.ModifyAssetPaths(layer, modify_fn)
    if layer.dirty:
        layer.Save()

get_drive(path)

Return disk drive from path

Source code in client/ayon_usd/plugins/publish/integrate_usd_output_processor_remap_relative.py
20
21
22
def get_drive(path) -> str:
    """Return disk drive from path"""
    return os.path.splitdrive(path)[0]