Skip to content

extract_multiverse_usd_comp

ExtractMultiverseUsdComposition

Bases: MayaExtractorPlugin

Extractor of Multiverse USD Composition data.

This will extract settings for a Multiverse Write Composition operation: they are visible in the Maya set node created by a Multiverse USD Composition instance creator.

The input data contained in the set is either:

  • a single hierarchy consisting of several Multiverse Compound nodes, with any number of layers, and Maya transform nodes
  • a single Compound node with more than one layer (in this case the "Write as Compound Layers" option should be set).

Upon publish a .usda composition file will be written.

Source code in client/ayon_maya/plugins/publish/extract_multiverse_usd_comp.py
  8
  9
 10
 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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
class ExtractMultiverseUsdComposition(plugin.MayaExtractorPlugin):
    """Extractor of Multiverse USD Composition data.

    This will extract settings for a Multiverse Write Composition operation:
    they are visible in the Maya set node created by a Multiverse USD
    Composition instance creator.

    The input data contained in the set is either:

    - a single hierarchy consisting of several Multiverse Compound nodes, with
      any number of layers, and Maya transform nodes
    - a single Compound node with more than one layer (in this case the "Write
      as Compound Layers" option should be set).

    Upon publish a .usda composition file will be written.
    """

    label = "Extract Multiverse USD Composition"
    families = ["mvUsdComposition"]
    scene_type = "usd"
    # Order of `fileFormat` must match create_multiverse_usd_comp.py
    file_formats = ["usda", "usd"]

    @property
    def options(self):
        """Overridable options for Multiverse USD Export

        Given in the following format
            - {NAME: EXPECTED TYPE}

        If the overridden option's type does not match,
        the option is not included and a warning is logged.

        """

        return {
            "stripNamespaces": bool,
            "mergeTransformAndShape": bool,
            "flattenContent": bool,
            "writeAsCompoundLayers": bool,
            "writePendingOverrides": bool,
            "numTimeSamples": int,
            "timeSamplesSpan": float
        }

    @property
    def default_options(self):
        """The default options for Multiverse USD extraction."""

        return {
            "stripNamespaces": True,
            "mergeTransformAndShape": False,
            "flattenContent": False,
            "writeAsCompoundLayers": False,
            "writePendingOverrides": False,
            "numTimeSamples": 1,
            "timeSamplesSpan": 0.0
        }

    def parse_overrides(self, instance, options):
        """Inspect data of instance to determine overridden options"""

        for key in instance.data:
            if key not in self.options:
                continue

            # Ensure the data is of correct type
            value = instance.data[key]
            if not isinstance(value, self.options[key]):
                self.log.warning(
                    "Overridden attribute {key} was of "
                    "the wrong type: {invalid_type} "
                    "- should have been {valid_type}".format(
                        key=key,
                        invalid_type=type(value).__name__,
                        valid_type=self.options[key].__name__))
                continue

            options[key] = value

        return options

    def process(self, instance):
        # Load plugin first
        cmds.loadPlugin("MultiverseForMaya", quiet=True)

        # Define output file path
        staging_dir = self.staging_dir(instance)
        file_format = instance.data.get("fileFormat", 0)
        if file_format in range(len(self.file_formats)):
            self.scene_type = self.file_formats[file_format]
        file_name = "{0}.{1}".format(instance.name, self.scene_type)
        file_path = os.path.join(staging_dir, file_name)
        file_path = file_path.replace('\\', '/')

        # Parse export options
        options = self.default_options
        options = self.parse_overrides(instance, options)
        self.log.debug("Export options: {0}".format(options))

        # Perform extraction
        self.log.debug("Performing extraction ...")

        with maintained_selection():
            members = instance.data("setMembers")
            self.log.debug('Collected object {}'.format(members))

            import multiverse

            time_opts = None
            frame_start = instance.data['frameStart']
            frame_end = instance.data['frameEnd']
            handle_start = instance.data['handleStart']
            handle_end = instance.data['handleEnd']
            step = instance.data['step']
            fps = instance.data['fps']
            if frame_end != frame_start:
                time_opts = multiverse.TimeOptions()

                time_opts.writeTimeRange = True
                time_opts.frameRange = (
                    frame_start - handle_start, frame_end + handle_end)
                time_opts.frameIncrement = step
                time_opts.numTimeSamples = instance.data["numTimeSamples"]
                time_opts.timeSamplesSpan = instance.data["timeSamplesSpan"]
                time_opts.framePerSecond = fps

            comp_write_opts = multiverse.CompositionWriteOptions()

            """
            OP tells MV to write to a staging directory, and then moves the
            file to it's final publish directory. By default, MV write relative
            paths, but these paths will break when the referencing file moves.
            This option forces writes to absolute paths, which is ok within OP
            because all published assets have static paths, and MV can only
            reference published assets. When a proper UsdAssetResolver is used,
            this won't be needed.
            """
            comp_write_opts.forceAbsolutePaths = True

            options_discard_keys = {
                'numTimeSamples',
                'timeSamplesSpan',
                'frameStart',
                'frameEnd',
                'handleStart',
                'handleEnd',
                'step',
                'fps'
            }
            for key, value in options.items():
                if key in options_discard_keys:
                    continue
                setattr(comp_write_opts, key, value)

            multiverse.WriteComposition(file_path, members, comp_write_opts)

        if "representations" not in instance.data:
            instance.data["representations"] = []

        representation = {
            'name': self.scene_type,
            'ext': self.scene_type,
            'files': file_name,
            'stagingDir': staging_dir
        }
        instance.data["representations"].append(representation)

        self.log.debug("Extracted instance {} to {}".format(instance.name,
                                                            file_path))

default_options property

The default options for Multiverse USD extraction.

options property

Overridable options for Multiverse USD Export

Given in the following format - {NAME: EXPECTED TYPE}

If the overridden option's type does not match, the option is not included and a warning is logged.

parse_overrides(instance, options)

Inspect data of instance to determine overridden options

Source code in client/ayon_maya/plugins/publish/extract_multiverse_usd_comp.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def parse_overrides(self, instance, options):
    """Inspect data of instance to determine overridden options"""

    for key in instance.data:
        if key not in self.options:
            continue

        # Ensure the data is of correct type
        value = instance.data[key]
        if not isinstance(value, self.options[key]):
            self.log.warning(
                "Overridden attribute {key} was of "
                "the wrong type: {invalid_type} "
                "- should have been {valid_type}".format(
                    key=key,
                    invalid_type=type(value).__name__,
                    valid_type=self.options[key].__name__))
            continue

        options[key] = value

    return options