Skip to content

load_reference

MayaUSDReferenceLoader

Bases: ReferenceLoader

Reference USD file to native Maya nodes using MayaUSDImport reference

Source code in client/ayon_maya/plugins/load/load_reference.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
class MayaUSDReferenceLoader(ReferenceLoader):
    """Reference USD file to native Maya nodes using MayaUSDImport reference"""

    label = "Reference Maya USD"
    product_types = {"usd"}
    representations = {"usd"}
    extensions = {"usd", "usda", "usdc"}

    options = ReferenceLoader.options + [
        qargparse.Boolean(
            "readAnimData",
            label="Load anim data",
            default=True,
            help="Load animation data from USD file"
        ),
        qargparse.Boolean(
            "useAsAnimationCache",
            label="Use as animation cache",
            default=True,
            help=(
                "Imports geometry prims with time-sampled point data using a "
                "point-based deformer that references the imported "
                "USD file.\n"
                "This provides better import and playback performance when "
                "importing time-sampled geometry from USD, and should "
                "reduce the weight of the resulting Maya scene."
            )
        ),
        qargparse.Boolean(
            "importInstances",
            label="Import instances",
            default=True,
            help=(
                "Import USD instanced geometries as Maya instanced shapes. "
                "Will flatten the scene otherwise."
            )
        ),
        qargparse.String(
            "primPath",
            label="Prim Path",
            default="/",
            help=(
                "Name of the USD scope where traversing will begin.\n"
                "The prim at the specified primPath (including the prim) will "
                "be imported.\n"
                "Specifying the pseudo-root (/) means you want "
                "to import everything in the file.\n"
                "If the passed prim path is empty, it will first try to "
                "import the defaultPrim for the rootLayer if it exists.\n"
                "Otherwise, it will behave as if the pseudo-root was passed "
                "in."
            )
        )
    ]

    file_type = "USD Import"

    def process_reference(self, context, name, namespace, options):
        cmds.loadPlugin("mayaUsdPlugin", quiet=True)

        def bool_option(key, default):
            # Shorthand for getting optional boolean file option from options
            value = int(bool(options.get(key, default)))
            return "{}={}".format(key, value)

        def string_option(key, default):
            # Shorthand for getting optional string file option from options
            value = str(options.get(key, default))
            return "{}={}".format(key, value)

        options["file_options"] = ";".join([
            string_option("primPath", default="/"),
            bool_option("importInstances", default=True),
            bool_option("useAsAnimationCache", default=True),
            bool_option("readAnimData", default=True),
            # TODO: Expose more parameters
            # "preferredMaterial=none",
            # "importRelativeTextures=Automatic",
            # "useCustomFrameRange=0",
            # "startTime=0",
            # "endTime=0",
            # "importUSDZTextures=0"
        ])
        options["file_type"] = self.file_type

        # Maya USD import reference has the tendency to change the time slider
        # range and current frame, so we force revert it after
        with preserve_time_units():
            return super(MayaUSDReferenceLoader, self).process_reference(
                context, name, namespace, options
            )

    def update(self, container, context):
        # Maya USD import reference has the tendency to change the time slider
        # range and current frame, so we force revert it after
        with preserve_time_units():
            super(MayaUSDReferenceLoader, self).update(container, context)

ReferenceLoader

Bases: ReferenceLoader

Reference file

Source code in client/ayon_maya/plugins/load/load_reference.py
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
class ReferenceLoader(plugin.ReferenceLoader):
    """Reference file"""

    product_types = {
        "model",
        "pointcache",
        "proxyAbc",
        "animation",
        "mayaAscii",
        "mayaScene",
        "setdress",
        "layout",
        "camera",
        "rig",
        "camerarig",
        "staticMesh",
        "skeletalMesh",
        "mvLook",
        "matchmove",
    }

    representations = {"ma", "abc", "fbx", "mb"}

    label = "Reference"
    order = -10
    icon = "code-fork"
    color = "orange"

    def process_reference(self, context, name, namespace, options):
        import maya.cmds as cmds

        product_type = context["product"]["productType"]
        project_name = context["project"]["name"]
        # True by default to keep legacy behaviours
        attach_to_root = options.get("attach_to_root", True)
        group_name = options["group_name"]

        # no group shall be created
        if not attach_to_root:
            group_name = namespace

        kwargs = {}
        if "file_options" in options:
            kwargs["options"] = options["file_options"]
        if "file_type" in options:
            kwargs["type"] = options["file_type"]

        path = self.filepath_from_context(context)
        with maintained_selection():
            cmds.loadPlugin("AbcImport.mll", quiet=True)

            file_url = self.prepare_root_value(path, project_name)
            nodes = cmds.file(file_url,
                              namespace=namespace,
                              sharedReferenceFile=False,
                              reference=True,
                              returnNewNodes=True,
                              groupReference=attach_to_root,
                              groupName=group_name,
                              **kwargs)

            shapes = cmds.ls(nodes, shapes=True, long=True)

            new_nodes = (list(set(nodes) - set(shapes)))

            # if there are cameras, try to lock their transforms
            self._lock_camera_transforms(new_nodes)

            current_namespace = cmds.namespaceInfo(currentNamespace=True)

            if current_namespace != ":":
                group_name = current_namespace + ":" + group_name

            self[:] = new_nodes

            if attach_to_root:
                group_name = "|" + group_name
                roots = cmds.listRelatives(group_name,
                                           children=True,
                                           fullPath=True) or []

                if product_type not in {
                    "layout", "setdress", "mayaAscii", "mayaScene"
                }:
                    # QUESTION Why do we need to exclude these families?
                    with parent_nodes(roots, parent=None):
                        cmds.xform(group_name, zeroTransformPivots=True)

                settings = get_project_settings(project_name)
                color = plugin.get_load_color_for_product_type(
                    product_type, settings
                )
                if color is not None:
                    red, green, blue = color
                    cmds.setAttr("{}.useOutlinerColor".format(group_name), 1)
                    cmds.setAttr(
                        "{}.outlinerColor".format(group_name),
                        red,
                        green,
                        blue
                    )

                display_handle = settings['maya']['load'].get(
                    'reference_loader', {}
                ).get('display_handle', True)
                if display_handle:
                    self._set_display_handle(group_name)

            if product_type == "rig":
                self._post_process_rig(namespace, context, options)
            else:
                if "translate" in options:
                    if not attach_to_root and new_nodes:
                        root_nodes = cmds.ls(new_nodes, assemblies=True,
                                             long=True)
                        # we assume only a single root is ever loaded
                        group_name = root_nodes[0]
                    cmds.setAttr("{}.translate".format(group_name),
                                 *options["translate"])
            return new_nodes

    def switch(self, container, context):
        self.update(container, context)

    def update(self, container, context):
        with preserve_modelpanel_cameras(container, log=self.log):
            super(ReferenceLoader, self).update(container, context)

        # We also want to lock camera transforms on any new cameras in the
        # reference or for a camera which might have changed names.
        members = get_container_members(container)
        self._lock_camera_transforms(members)

    def _post_process_rig(self, namespace, context, options):

        nodes = self[:]
        try:
            create_rig_animation_instance(
                nodes, context, namespace, options=options, log=self.log
            )
        except RigSetsNotExistError as exc:
            self.log.warning(
                "Missing rig sets for animation instance creation: %s", exc)

    def _lock_camera_transforms(self, nodes):
        cameras = cmds.ls(nodes, type="camera")
        if not cameras:
            return

        # Check the Maya version, lockTransform has been introduced since
        # Maya 2016.5 Ext 2
        version = int(cmds.about(version=True))
        if version >= 2016:
            for camera in cameras:
                cmds.camera(camera, edit=True, lockTransform=True)
        else:
            self.log.warning("This version of Maya does not support locking of"
                             " transforms of cameras.")

    def _set_display_handle(self, group_name: str):
        """Enable display handle and move select handle to object center"""
        cmds.setAttr(f"{group_name}.displayHandle", True)
        # get bounding box
        # Bugfix: We force a refresh here because there is a reproducable case
        # with Advanced Skeleton rig where the call to `exactWorldBoundingBox`
        # directly after the reference without it breaks the behavior of the
        # rigs making it appear as if parts of the mesh are static.
        # TODO: Preferably we have a better fix than requiring refresh on loads
        cmds.refresh()
        bbox = cmds.exactWorldBoundingBox(group_name)
        # get pivot position on world space
        pivot = cmds.xform(group_name, q=True, sp=True, ws=True)
        # center of bounding box
        cx = (bbox[0] + bbox[3]) / 2
        cy = (bbox[1] + bbox[4]) / 2
        cz = (bbox[2] + bbox[5]) / 2
        # add pivot position to calculate offset
        cx += pivot[0]
        cy += pivot[1]
        cz += pivot[2]
        # set selection handle offset to center of bounding box
        cmds.setAttr(f"{group_name}.selectHandleX", cx)
        cmds.setAttr(f"{group_name}.selectHandleY", cy)
        cmds.setAttr(f"{group_name}.selectHandleZ", cz)

preserve_modelpanel_cameras(container, log=None)

Preserve camera members of container in the modelPanels.

This is used to ensure a camera remains in the modelPanels after updating to a new version.

Source code in client/ayon_maya/plugins/load/load_reference.py
 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
@contextlib.contextmanager
def preserve_modelpanel_cameras(container, log=None):
    """Preserve camera members of container in the modelPanels.

    This is used to ensure a camera remains in the modelPanels after updating
    to a new version.

    """

    # Get the modelPanels that used the old camera
    members = get_container_members(container)
    old_cameras = set(cmds.ls(members, type="camera", long=True))
    if not old_cameras:
        # No need to manage anything
        yield
        return

    panel_cameras = {}
    for panel in cmds.getPanel(type="modelPanel"):
        cam = cmds.ls(cmds.modelPanel(panel, query=True, camera=True),
                      long=True)[0]

        # Often but not always maya returns the transform from the
        # modelPanel as opposed to the camera shape, so we convert it
        # to explicitly be the camera shape
        if cmds.nodeType(cam) != "camera":
            cam = cmds.listRelatives(cam,
                                     children=True,
                                     fullPath=True,
                                     type="camera")[0]
        if cam in old_cameras:
            panel_cameras[panel] = cam

    if not panel_cameras:
        # No need to manage anything
        yield
        return

    try:
        yield
    finally:
        new_members = get_container_members(container)
        new_cameras = set(cmds.ls(new_members, type="camera", long=True))
        if not new_cameras:
            return

        for panel, cam_name in panel_cameras.items():
            new_camera = None
            if cam_name in new_cameras:
                new_camera = cam_name
            elif len(new_cameras) == 1:
                new_camera = next(iter(new_cameras))
            else:
                # Multiple cameras in the updated container but not an exact
                # match detected by name. Find the closest match
                matches = difflib.get_close_matches(word=cam_name,
                                                    possibilities=new_cameras,
                                                    n=1)
                if matches:
                    new_camera = matches[0]  # best match
                    if log:
                        log.info("Camera in '{}' restored with "
                                 "closest match camera: {} (before: {})"
                                 .format(panel, new_camera, cam_name))

            if not new_camera:
                # Unable to find the camera to re-apply in the modelpanel
                continue

            cmds.modelPanel(panel, edit=True, camera=new_camera)

preserve_time_units()

Preserve current frame, frame range and fps

Source code in client/ayon_maya/plugins/load/load_reference.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@contextlib.contextmanager
def preserve_time_units():
    """Preserve current frame, frame range and fps"""
    frame = cmds.currentTime(query=True)
    fps = cmds.currentUnit(query=True, time=True)
    start = cmds.playbackOptions(query=True, minTime=True)
    end = cmds.playbackOptions(query=True, maxTime=True)
    anim_start = cmds.playbackOptions(query=True, animationStartTime=True)
    anim_end = cmds.playbackOptions(query=True, animationEndTime=True)
    try:
        yield
    finally:
        cmds.currentUnit(time=fps, updateAnimation=False)
        cmds.currentTime(frame)
        cmds.playbackOptions(minTime=start,
                             maxTime=end,
                             animationStartTime=anim_start,
                             animationEndTime=anim_end)