Beispiel #1
0
        def verifyEditedScene():
            aMayaItem = ufe.GlobalSelection.get().front()
            aMayaPath = aMayaItem.path()
            aMayaPathStr = ufe.PathString.string(aMayaPath)

            # Confirm the hierarchy is preserved through the Hierarchy interface:
            # one child, the parent of the pulled item is the proxy shape, and
            # the proxy shape has the pulled item as a child, not the original USD
            # scene item.
            aMayaHier = ufe.Hierarchy.hierarchy(aMayaItem)
            self.assertEqual(len(aMayaHier.children()), 1)
            self.assertEqual(ps, aMayaHier.parent())
            psHier = ufe.Hierarchy.hierarchy(ps)
            self.assertIn(aMayaItem, psHier.children())
            self.assertNotIn(aUsdItem, psHier.children())

            # Confirm the translation has been transferred, and that the local
            # transformation is only a translation.
            aDagPath = om.MSelectionList().add(
                ufe.PathString.string(aMayaPath)).getDagPath(0)
            aFn = om.MFnTransform(aDagPath)
            self.assertEqual(aFn.translation(om.MSpace.kObject),
                             om.MVector(*xlation))
            mayaMatrix = aFn.transformation().asMatrix()
            usdMatrix = xlateOp.GetOpTransform(
                mayaUsd.ufe.getTime(aUsdUfePathStr))
            mayaValues = [v for v in mayaMatrix]
            usdValues = [v for row in usdMatrix for v in row]

            assertVectorAlmostEqual(self, mayaValues, usdValues)
Beispiel #2
0
    def testMergeToUsdToNonRootTargetInSessionLayer(self):
        '''Merge edits on a USD transform back to USD targeting a non-root destination path that
           does not exists in the destination layer.'''

        # To merge back to USD, we must edit as Maya first.
        (ps, aXlateOp, _, aUsdUfePathStr, aUsdUfePath, aUsdItem,
         bXlateOp, _, bUsdUfePathStr, bUsdUfePath, bUsdItem) = \
            createSimpleXformScene()

        with mayaUsd.lib.OpUndoItemList():
            self.assertTrue(
                mayaUsd.lib.PrimUpdaterManager.editAsMaya(bUsdUfePathStr))

        bMayaItem = ufe.GlobalSelection.get().front()
        (bMayaPath, bMayaPathStr, _, bMayaMatrix) = \
            setMayaTranslation(bMayaItem, om.MVector(10, 11, 12))

        psHier = ufe.Hierarchy.hierarchy(ps)

        # Merge edits back to USD.
        with mayaUsd.lib.OpUndoItemList():
            stage = mayaUsd.ufe.getStage(bUsdUfePathStr)
            stage.SetEditTarget(stage.GetSessionLayer())
            self.assertTrue(
                mayaUsd.lib.PrimUpdaterManager.mergeToUsd(bMayaPathStr))

        # Check that edits have been preserved in USD.
        bUsdMatrix = bXlateOp.GetOpTransform(
            mayaUsd.ufe.getTime(bUsdUfePathStr))
        mayaValues = [v for v in bMayaMatrix]
        usdValues = [v for row in bUsdMatrix for v in row]

        assertVectorAlmostEqual(self, mayaValues, usdValues)
Beispiel #3
0
        def checkParentDone():
            # The xform now has the capsule as its child.
            xformChildren = xformHier.children()
            self.assertEqual(len(xformChildren), 1)
            self.assertIn('Capsule1', childrenNames(xformChildren))

            # Confirm that the capsule has not moved in world space.  Must
            # re-create the prim and its scene item, as its path has changed.
            capsulePath = ufe.Path([
                proxyShapePathSegment,
                usdUtils.createUfePathSegment('/Xform1/Capsule1')
            ])
            capsulePrim = mayaUsd.ufe.ufePathToPrim(
                ufe.PathString.string(capsulePath))
            capsuleXformable = UsdGeom.Xformable(capsulePrim)
            capsuleItem = ufe.Hierarchy.createItem(capsulePath)
            capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
            capsuleWorld = capsuleT3d.inclusiveMatrix()
            assertVectorAlmostEqual(self, capsuleWorldPre,
                                    matrixToList(capsuleWorld))

            # The capsule's transform ops now have Maya fallback stack ops.  A
            # scale fallback op is added, even though it's uniform unit.
            self.assertEqual(
                capsuleXformable.GetXformOpOrderAttr().Get(),
                Vt.TokenArray(
                    ("xformOp:translate", "xformOp:rotateXYZ",
                     "xformOp:rotateX", "xformOp:translate:maya_fallback",
                     "xformOp:rotateXYZ:maya_fallback",
                     "xformOp:scale:maya_fallback")))
Beispiel #4
0
        def checkParentDone():
            # The xform now has the capsule as its child.
            xformChildren = xformHier.children()
            self.assertEqual(len(xformChildren), 1)
            self.assertIn('Capsule1', childrenNames(xformChildren))

            # Confirm that the capsule has not moved in world space.  Must
            # re-create the prim and its scene item, as its path has changed.
            capsulePath = ufe.Path([
                proxyShapePathSegment,
                usdUtils.createUfePathSegment('/Xform1/Capsule1')
            ])
            capsulePrim = mayaUsd.ufe.ufePathToPrim(
                ufe.PathString.string(capsulePath))
            capsuleXformable = UsdGeom.Xformable(capsulePrim)
            capsuleItem = ufe.Hierarchy.createItem(capsulePath)
            capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
            capsuleWorld = capsuleT3d.inclusiveMatrix()
            assertVectorAlmostEqual(self,
                                    capsuleWorldPre,
                                    matrixToList(capsuleWorld),
                                    places=6)
            # Using setMatrixCmd() on a common transform API prim will set
            # translate, rotate, and scale.
            self.assertEqual(
                capsuleXformable.GetXformOpOrderAttr().Get(),
                Vt.TokenArray(("xformOp:translate", "xformOp:translate:pivot",
                               "xformOp:rotateXYZ", "xformOp:scale",
                               "!invert!xformOp:translate:pivot")))
Beispiel #5
0
        def verifyMergeToUsd():
            # Edit will re-allocate anything that was under pulled prim due to deactivation
            # Grab new references for /A/B prim
            bUsdPrim = mayaUsd.ufe.ufePathToPrim(bUsdUfePathStr)
            bXlateOp = UsdGeom.Xformable(bUsdPrim).GetOrderedXformOps()[0]

            # Check that edits have been preserved in USD.
            for (usdUfePathStr, mayaMatrix, xlateOp) in \
                zip([aUsdUfePathStr, bUsdUfePathStr], [aMayaMatrix, bMayaMatrix],
                    [aXlateOp, bXlateOp]):
                usdMatrix = xlateOp.GetOpTransform(
                    mayaUsd.ufe.getTime(usdUfePathStr))
                mayaValues = [v for v in mayaMatrix]
                usdValues = [v for row in usdMatrix for v in row]

                assertVectorAlmostEqual(self, mayaValues, usdValues)

            # There no longer are any Maya to USD path mappings.
            for mayaPath in [aMayaPath, bMayaPath]:
                self.assertEqual(len(mayaToUsd.fromHost(mayaPath)), 0)

            # Hierarchy is restored: USD item is child of proxy shape, Maya item is
            # not.  Be careful to use the Maya path rather than the Maya item, which
            # should no longer exist.
            self.assertIn(aUsdItem, psHier.children())
            self.assertNotIn(aMayaPath,
                             [child.path() for child in psHier.children()])

            # Maya nodes are removed.
            for mayaPathStr in [aMayaPathStr, bMayaPathStr]:
                with self.assertRaises(RuntimeError):
                    om.MSelectionList().add(mayaPathStr)
Beispiel #6
0
    def snapshotRunTimeUFE(self):
        '''Return a pair with an op read from the run-time and from UFE.

        Tests that the op read from the run-time interface matches the
        UFE op.
        '''
        # Get translation
        #
        rtAll = None
        ufeAll = None
        offset = 0
        for op in self.ops:
            runTimeVec = self.runTimes[op]()
            ufeVec  = self.ufes[op]()
            if op == self.rotate:
                # The runtimes for rotate return an MEulerRotation, which we
                # must convert to a vector in degrees, since updateTRS expects
                # it in that format.
                #
                r = runTimeVec.asVector()
                rtAll = self.updateTRS( rtAll, op, [degrees(r[0]), degrees(r[1]), degrees(r[2])] )
                r = ufeVec.asVector()
                ufeAll = self.updateTRS( ufeAll, op, [degrees(r[0]), degrees(r[1]), degrees(r[2])] )
            else:
                rtAll = self.updateTRS( rtAll, op, runTimeVec )
                ufeAll = self.updateTRS( ufeAll, op, ufeVec )
            assertVectorAlmostEqual(self, runTimeVec, ufeVec)

        assertVectorAlmostEqual(self, rtAll, ufeAll)

        return (rtAll, ufeAll)
Beispiel #7
0
    def testMultiSelectMoveUSD(self):
        '''Move multiple USD objects, read through Transform3d interface.'''

        # Select multiple balls to move them.
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        balls = ['Ball_33', 'Ball_34']
        ballPaths = [
            ufe.Path([
                proxyShapePathSegment,
                usdUtils.createUfePathSegment('/Room_set/Props/' + ball)
            ]) for ball in balls
        ]
        ballItems = [
            ufe.Hierarchy.createItem(ballPath) for ballPath in ballPaths
        ]

        for ballItem in ballItems:
            ufe.GlobalSelection.get().append(ballItem)

        # We compare the UFE translation with the USD run-time translation.  To
        # obtain the full translation of USD scene items, we need to add the USD
        # translation to the Maya proxy shape translation.
        proxyShapeXformObj = om.MSelectionList().add('transform1').getDagPath(
            0).node()
        proxyShapeXformFn = om.MFnTransform(proxyShapeXformObj)

        def usdSceneItemTranslation(item):
            prim = usdUtils.getPrimFromSceneItem(item)
            if not prim.HasAttribute('xformOp:translate'):
                return proxyShapeXformFn.translation(om.MSpace.kTransform)
            else:
                return addVec(
                    proxyShapeXformFn.translation(om.MSpace.kTransform),
                    prim.GetAttribute('xformOp:translate').Get())

        def ufeSceneItemTranslation(item):
            return transform3dTranslation(ufe.Transform3d.transform3d(item))

        # Set up the callables that will retrieve the translation.
        self.runTimeTranslation = usdSceneItemTranslation
        self.ufeTranslation = ufeSceneItemTranslation

        # Give the tail item in the selection an initial translation that
        # is different, to catch bugs where the relative translation
        # incorrectly squashes any existing translation.
        backItem = ballItems[-1]
        backT3d = ufe.Transform3d.transform3d(backItem)
        initialTranslation = [-10, -20, -30]
        backT3d.translate(*initialTranslation)
        assertVectorAlmostEqual(self, ufeSceneItemTranslation(backItem),
                                usdSceneItemTranslation(backItem))

        # Save the initial positions to the memento list.
        expected = [
            usdSceneItemTranslation(ballItem) for ballItem in ballItems
        ]

        self.runMultiSelectTestMove(ballItems, expected)
Beispiel #8
0
    def testPurposeBoundingBox(self):
        '''Bounding box of prims with guide, proxy, and render purpose.'''
        # Create a scene with prims of purposes other than default: guide,
        # proxy, and render.  All must have a valid bounding box.  The bounding
        # box is conditional to the proxy shape on the UFE path to the prim
        # having that purpose enabled: if the purpose is disabled, the bounding
        # box is invalid.
        import mayaUsd_createStageWithNewLayer

        proxyShapePath = mayaUsd_createStageWithNewLayer.createStageWithNewLayer(
        )
        proxyShapePathSegment = mayaUtils.createUfePathSegment(proxyShapePath)
        stage = mayaUsd.lib.GetPrim(proxyShapePath).GetStage()
        usdPaths = ['/Cube1', '/Cube2', '/Cube3']
        prims = [stage.DefinePrim(path, 'Cube') for path in usdPaths]
        purposes = [
            UsdGeom.Tokens.proxy, UsdGeom.Tokens.guide, UsdGeom.Tokens.render
        ]
        for (prim, purpose) in zip(prims, purposes):
            imageable = UsdGeom.Imageable(prim)
            imageable.CreatePurposeAttr(purpose)

        # Create a UFE scene item for each prim, and get the bounding box using
        # the Object3d interface.
        for (prim, usdPath) in zip(prims, usdPaths):
            pathSegment = usdUtils.createUfePathSegment(usdPath)
            path = ufe.Path([proxyShapePathSegment, pathSegment])
            item = ufe.Hierarchy.createItem(path)
            object3d = ufe.Object3d.object3d(item)

            # First turn off proxy, guide, render purposes on the proxy shape.
            # The bounding box should be invalid.
            purposeAttribs = [
                'drawProxyPurpose', 'drawGuidePurpose', 'drawRenderPurpose'
            ]
            for purposeAttrib in purposeAttribs:
                cmds.setAttr(proxyShapePath + '.' + purposeAttrib, 0)

            bbox = object3d.boundingBox()

            self.assertTrue(bbox.empty())

            # Next, turn on each purpose in turn on the proxy shape.  The
            # bounding box should be valid only if the prim's purpose matches
            # the proxy shape purpose.
            imageable = UsdGeom.Imageable(prim)
            primPurpose = imageable.GetPurposeAttr().Get()
            for (purpose, purposeAttrib) in zip(purposes, purposeAttribs):
                cmds.setAttr(proxyShapePath + '.' + purposeAttrib, 1)

                bbox = object3d.boundingBox()

                if primPurpose == purpose:
                    assertVectorAlmostEqual(self, bbox.min.vector, [-1] * 3)
                    assertVectorAlmostEqual(self, bbox.max.vector, [1] * 3)
                else:
                    self.assertTrue(bbox.empty())

                cmds.setAttr(proxyShapePath + '.' + purposeAttrib, 0)
    def assertRunTimeUFEAlmostEqual(self):
        '''Tests that the translation read from the run-time interface matches the UFE translation.

        Returns a pair with translation read from the run-time and from UFE.
        '''
        runTimeVec = self.getRunTimeTranslation()
        ufeVec = self.getUfeTranslation()
        assertVectorAlmostEqual(self, runTimeVec, ufeVec)
Beispiel #10
0
 def checkTransform(expectedTranslation):
     # Check through both USD and UFE interfaces
     assertVectorAlmostEqual(self,
                             sphereT3d.translation().vector,
                             expectedTranslation)
     expected = Gf.Matrix4d(1.0)
     expected.SetTranslate(expectedTranslation)
     actual = sphereXformable.GetLocalTransformation()
     self.assertTrue(Gf.IsClose(actual, expected, 1e-5))
Beispiel #11
0
    def snapshotRunTimeUFE(self):
        '''Return a pair with scale read from the run-time and from UFE.

        Tests that the scale read from the run-time interface matches the
        UFE scale.
        '''
        runTimeVec = self.runTimeScale()
        ufeVec = self.ufeScale()
        assertVectorAlmostEqual(self, runTimeVec, ufeVec, places=6)
        return (runTimeVec, ufeVec)
Beispiel #12
0
    def snapShotAndTest(self, expected, places=7):
        '''Take a snapshot of the state and append it to the memento list.

        The snapshot is compared to the expected results.
        '''
        snapshot = self.snapshotRunTimeUFE()
        self.memento.append(snapshot)
        # Since snapshotRunTimeUFE checks run-time to UFE equality, we can use
        # the UFE or the run-time value to check against expected.
        assertVectorAlmostEqual(self, snapshot[1], expected, places)
Beispiel #13
0
    def snapshotRunTimeUFE(self):
        '''Return a pair with translation read from the run-time and from UFE.

        Tests that the translation read from the run-time interface matches the
        UFE translation.
        '''
        runTimeVec = self.runTimeTranslation()
        ufeVec  = self.ufeTranslation()
        assertVectorAlmostEqual(self, runTimeVec, ufeVec)
        return (runTimeVec, ufeVec)
Beispiel #14
0
def checkWorldSpaceXform(testCase, objects):
    '''Confirm matching Maya and UFE object world space positions.

    The Maya object is the first object in the objects argument, and is used
    as the benchmark.'''

    mayaWorld = cmds.xform(objects[0], q=True, ws=True, matrix=True)
    for t3d in objects[1:]:
        # Flatten out UFE matrices for comparison with Maya output.
        usdWorld = [y for x in t3d.inclusiveMatrix().matrix for y in x]
        assertVectorAlmostEqual(testCase, mayaWorld, usdWorld)
Beispiel #15
0
    def testAnimatedBoundingBox(self):
        '''Test the Object3d bounding box interface for animated geometry.'''

        # Open sphereAnimatedRadiusProxyShape.ma scene in testSamples
        mayaUtils.openSphereAnimatedRadiusScene()

        # The extents of the sphere are copied from the .usda file.
        expected = [[(-1.0000002, -1, -1.0000005), (1, 1, 1.0000001)],
                    [(-1.3086424, -1.308642, -1.3086426),
                     (1.308642, 1.308642, 1.3086421)],
                    [(-2.135803, -2.1358025, -2.1358035),
                     (2.1358025, 2.1358025, 2.1358027)],
                    [(-3.333334, -3.3333333, -3.333335),
                     (3.3333333, 3.3333333, 3.3333337)],
                    [(-4.7530875, -4.7530866, -4.753089),
                     (4.7530866, 4.7530866, 4.753087)],
                    [(-6.246915, -6.2469134, -6.2469163),
                     (6.2469134, 6.2469134, 6.2469144)],
                    [(-7.6666684, -7.6666665, -7.6666703),
                     (7.6666665, 7.6666665, 7.6666675)],
                    [(-8.8642, -8.864198, -8.864202),
                     (8.864198, 8.864198, 8.864199)],
                    [(-9.6913595, -9.691358, -9.691362),
                     (9.691358, 9.691358, 9.691359)],
                    [(-10.000002, -10, -10.000005), (10, 10, 10.000001)]]

        # Create an Object3d interface for USD sphere.
        mayaPathSegment = mayaUtils.createUfePathSegment(
            '|transform1|proxyShape1')
        usdPathSegment = usdUtils.createUfePathSegment('/pSphere1')

        spherePath = ufe.Path([mayaPathSegment, usdPathSegment])
        sphereItem = ufe.Hierarchy.createItem(spherePath)

        object3d = ufe.Object3d.object3d(sphereItem)

        # Loop over frames 1 to 10, and compare the values returned to the
        # expected values.
        for frame in range(1, 11):
            cmds.currentTime(frame)

            ufeBBox = object3d.boundingBox()

            # Compare it to known extents.
            assertVectorAlmostEqual(self,
                                    ufeBBox.min.vector,
                                    expected[frame - 1][0],
                                    places=6)
            assertVectorAlmostEqual(self,
                                    ufeBBox.max.vector,
                                    expected[frame - 1][1],
                                    places=6)
Beispiel #16
0
    def testTransformEditAsMaya(self):
        '''Edit a USD transform as a Maya object.'''

        (ps, xlateOp, xlation, aUsdUfePathStr, aUsdUfePath, aUsdItem, _, _, _,
         _, _) = createSimpleXformScene()

        # Edit aPrim as Maya data.
        with mayaUsd.lib.OpUndoItemList():
            self.assertTrue(
                mayaUsd.lib.PrimUpdaterManager.canEditAsMaya(aUsdUfePathStr))
            self.assertTrue(
                mayaUsd.lib.PrimUpdaterManager.editAsMaya(aUsdUfePathStr))

        # Test the path mapping services.
        #
        # Unfortunately, toHost is unimplemented as of 20-Sep-2021.  Use the
        # selection to retrieve the Maya object.
        #
        # usdToMaya = ufe.PathMappingHandler.pathMappingHandler(aUsdItem)
        # aMayaUfePath = usdToMaya.toHost(aUsdUfePath)
        # self.assertEqual(aMayaUfePath.nbSegments(), 1)

        aMayaItem = ufe.GlobalSelection.get().front()
        aMayaPath = aMayaItem.path()
        self.assertEqual(aMayaPath.nbSegments(), 1)
        mayaToUsd = ufe.PathMappingHandler.pathMappingHandler(aMayaItem)
        self.assertEqual(mayaToUsd.fromHost(aMayaPath), aUsdUfePath)

        # Confirm the hierarchy is preserved through the Hierarchy interface:
        # one child, the parent of the pulled item is the proxy shape, and
        # the proxy shape has the pulled item as a child, not the original USD
        # scene item.
        aMayaHier = ufe.Hierarchy.hierarchy(aMayaItem)
        self.assertEqual(len(aMayaHier.children()), 1)
        self.assertEqual(ps, aMayaHier.parent())
        psHier = ufe.Hierarchy.hierarchy(ps)
        self.assertIn(aMayaItem, psHier.children())
        self.assertNotIn(aUsdItem, psHier.children())

        # Confirm the translation has been transferred, and that the local
        # transformation is only a translation.
        aDagPath = om.MSelectionList().add(
            ufe.PathString.string(aMayaPath)).getDagPath(0)
        aFn = om.MFnTransform(aDagPath)
        self.assertEqual(aFn.translation(om.MSpace.kObject),
                         om.MVector(*xlation))
        mayaMatrix = aFn.transformation().asMatrix()
        usdMatrix = xlateOp.GetOpTransform(mayaUsd.ufe.getTime(aUsdUfePathStr))
        mayaValues = [v for v in mayaMatrix]
        usdValues = [v for row in usdMatrix for v in row]

        assertVectorAlmostEqual(self, mayaValues, usdValues)
Beispiel #17
0
    def multiSelectSnapshotRunTimeUFE(self, items):
        '''Return a list of pairs with scale read from the run-time and from UFE.

        Tests that the scale read from the run-time interface matches the
        UFE scale.
        '''
        snapshot = []
        for item in items:
            runTimeVec = self.runTimeScale(item)
            ufeVec = self.ufeScale(item)
            assertVectorAlmostEqual(self, runTimeVec, ufeVec, places=6)
            snapshot.append((runTimeVec, ufeVec))
        return snapshot
Beispiel #18
0
    def multiSelectSnapShotAndTest(self, items, expected, places=7):
        '''Take a snapshot of the state and append it to the memento list.

        The snapshot is compared to the expected results.
        '''
        snapshot = self.multiSelectSnapshotRunTimeUFE(items)
        self.memento.append(snapshot)
        for (itemSnapshot, itemExpected) in zip(snapshot, expected):
            # Since multiSelectSnapshotRunTimeUFE checks run-time to UFE
            # equality, we can use the UFE or the run-time value to
            # check against expected.
            assertVectorAlmostEqual(self, itemSnapshot[1], itemExpected,
                                    places)
Beispiel #19
0
    def multiSelectSnapshotRunTimeUFE(self, items):
        '''Return a list of pairs with translation read from the run-time and from UFE.

        Tests that the translation read from the run-time interface matches the
        UFE translation.
        '''
        snapshot = []
        for item in items:
            runTimeVec = self.runTimeTranslation(item)
            ufeVec  = self.ufeTranslation(item)
            assertVectorAlmostEqual(self, runTimeVec, ufeVec)
            snapshot.append((runTimeVec, ufeVec))
        return snapshot
Beispiel #20
0
def checkPivotsAndCompensations(testCase, mayaObjName, usdT3d):
    '''Confirm matching Maya and UFE object pivots and pivot compensations.'''

    # getAttr() returns a single-element vector that holds a 3-element tuple.
    assertVectorAlmostEqual(testCase, cmds.getAttr(mayaObjName+".rp")[0],
                            usdT3d.rotatePivot().vector, places=6)
    assertVectorAlmostEqual(testCase, cmds.getAttr(mayaObjName+".sp")[0],
                            usdT3d.scalePivot().vector, places=6)
    assertVectorAlmostEqual(testCase, cmds.getAttr(mayaObjName+".rpt")[0],
                            usdT3d.rotatePivotTranslation().vector, places=6)
    assertVectorAlmostEqual(testCase, cmds.getAttr(mayaObjName+".spt")[0],
                            usdT3d.scalePivotTranslation().vector, places=6)
Beispiel #21
0
    def testTransform3dMatrixOpAccessors(self):
        '''Matrix transform op TRS must match separate transform op TRS.'''

        testFile = testUtils.getTestScene("transform3d", "TranslateRotate_vs_xform.usda")
        (psPath, stage) = mayaUtils.createProxyFromFile(testFile)

        # cam1 has the "transform" transform op (i.e. a matrix), and cam2 has
        # translate + rotateXYZ transform ops.
        cam1 = ufe.Hierarchy.createItem(
            ufe.PathString.path(psPath + ",/cameras/cam1"))
        cam2 = ufe.Hierarchy.createItem(
            ufe.PathString.path(psPath + ",/cameras/cam2"))
        cam1t3d = ufe.Transform3d.transform3d(cam1)
        cam2t3d = ufe.Transform3d.transform3d(cam2)
        cam1T = cam1t3d.translation()
        cam2T = cam2t3d.translation()
        testUtils.assertVectorAlmostEqual(self, cam1T.vector, cam2T.vector)
        cam1R = cam1t3d.rotation()
        cam2R = cam2t3d.rotation()
        testUtils.assertVectorAlmostEqual(self, cam1R.vector, cam2R.vector)
Beispiel #22
0
            def checkParentDone():
                # The xform now has the capsule as its child.
                xformChildren = xformHier.children()
                self.assertEqual(len(xformChildren), 1)
                self.assertIn('Capsule1', childrenNames(xformChildren))

                # Confirm that the capsule has not moved in world space.  Must
                # re-create the prim and its scene item after path change.
                capsulePath = ufe.Path([
                    proxyShapePathSegment,
                    usdUtils.createUfePathSegment('/Xform1/Capsule1')
                ])
                capsulePrim = mayaUsd.ufe.ufePathToPrim(
                    ufe.PathString.string(capsulePath))
                capsuleXformable = UsdGeom.Xformable(capsulePrim)
                capsuleItem = ufe.Hierarchy.createItem(capsulePath)
                capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
                capsuleWorld = capsuleT3d.inclusiveMatrix()
                assertVectorAlmostEqual(self, capsuleWorldPre,
                                        matrixToList(capsuleWorld))

                # No change in the capsule's transform ops.
                self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                                 Vt.TokenArray(matrixOps))

                # Matrix ops that were not targeted did not change.
                for checkMatrixOp in matrixOps:
                    if checkMatrixOp == matrixOp:
                        continue
                    op = UsdGeom.XformOp(
                        capsulePrim.GetAttribute(checkMatrixOp))
                    self.assertEqual(
                        op.GetOpTransform(
                            mayaUsd.ufe.getTime(
                                ufe.PathString.string(capsulePath))),
                        matrixValues[checkMatrixOp])
Beispiel #23
0
    def testCenterPivotUndo(self):

        cmds.file(new=True, force=True)

        import mayaUsd_createStageWithNewLayer
        mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        proxyShapePath = ufe.PathString.path('|stage1|stageShape1')
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])

        capsulePath = ufe.PathString.path('|stage1|stageShape1,/Capsule1')
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)
        usdT3d = ufe.Transform3d.transform3d(capsuleItem)

        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(capsuleItem)

        # center point is expected to be at [0.0, 0.0, 0.0]
        assertVectorAlmostEqual(self,
                                usdT3d.rotatePivot().vector, [0.0, 0.0, 0.0])
        assertVectorAlmostEqual(self,
                                usdT3d.scalePivot().vector, [0.0, 0.0, 0.0])

        # move the pivot location
        cmds.move(7, 2, 1, r=True, urp=True, usp=True)

        assertVectorAlmostEqual(self,
                                usdT3d.rotatePivot().vector, [7.0, 2.0, 1.0])
        assertVectorAlmostEqual(self,
                                usdT3d.scalePivot().vector, [7.0, 2.0, 1.0])

        # call center pivot command
        cmds.xform(cp=True)

        # center point is expected to be at [0.0, 0.0, 0.0]
        assertVectorAlmostEqual(self,
                                usdT3d.rotatePivot().vector, [0.0, 0.0, 0.0])
        assertVectorAlmostEqual(self,
                                usdT3d.scalePivot().vector, [0.0, 0.0, 0.0])

        # undo
        cmds.undo()

        assertVectorAlmostEqual(self,
                                usdT3d.rotatePivot().vector, [7.0, 2.0, 1.0])
        assertVectorAlmostEqual(self,
                                usdT3d.scalePivot().vector, [7.0, 2.0, 1.0])

        # redo
        cmds.redo()

        assertVectorAlmostEqual(self,
                                usdT3d.rotatePivot().vector, [0.0, 0.0, 0.0])
        assertVectorAlmostEqual(self,
                                usdT3d.scalePivot().vector, [0.0, 0.0, 0.0])
Beispiel #24
0
    def testParentRelative(self):
        # Create scene items for the cube and the cylinder.
        shapeSegment = mayaUtils.createUfePathSegment(
            "|mayaUsdProxy1|mayaUsdProxyShape1")
        cubePath = ufe.Path(
            [shapeSegment, usdUtils.createUfePathSegment("/cubeXform")])
        cubeItem = ufe.Hierarchy.createItem(cubePath)
        cylinderPath = ufe.Path(
            [shapeSegment, usdUtils.createUfePathSegment("/cylinderXform")])
        cylinderItem = ufe.Hierarchy.createItem(cylinderPath)

        # get the USD stage
        stage = mayaUsd.ufe.getStage(str(shapeSegment))

        # check GetLayerStack behavior
        self.assertEqual(stage.GetEditTarget().GetLayer(), stage.GetRootLayer())

        # The cube is not a child of the cylinder.
        cylHier = ufe.Hierarchy.hierarchy(cylinderItem)
        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)

        # Get the components of the cube's local transform.
        cubeT3d = ufe.Transform3d.transform3d(cubeItem)
        cubeT = cubeT3d.translation()
        cubeR = cubeT3d.rotation()
        cubeS = cubeT3d.scale()

        # Parent cube to cylinder in relative mode, using UFE path strings.
        cmds.parent("|mayaUsdProxy1|mayaUsdProxyShape1,/cubeXform",
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/cylinderXform",
                    relative=True)

        # Confirm that the cube is now a child of the cylinder.
        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 2)
        self.assertIn("cubeXform", childrenNames(cylChildren))

        # Undo: the cube is no longer a child of the cylinder.
        cmds.undo()

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)

        # Redo: confirm that the cube is again a child of the cylinder.
        cmds.redo()

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 2)
        self.assertIn("cubeXform", childrenNames(cylChildren))

        # Confirm that the cube's local transform has not changed.  Must
        # re-create the item, as its path has changed.
        cubeChildPath = ufe.Path(
            [shapeSegment, usdUtils.createUfePathSegment("/cylinderXform/cubeXform")])
        cubeChildItem = ufe.Hierarchy.createItem(cubeChildPath)
        cubeChildT3d = ufe.Transform3d.transform3d(cubeChildItem)

        self.assertEqual(cubeChildT3d.translation(), cubeT)
        self.assertEqual(cubeChildT3d.rotation(),    cubeR)
        self.assertEqual(cubeChildT3d.scale(),       cubeS)

        # Move the parent
        ufe.GlobalSelection.get().append(cylinderItem)

        cmds.move(0, 10, 0, relative=True)

        # Do the same on the equivalent Maya objects.
        cmds.parent("pCube1", "pCylinder1", relative=True)
        cmds.move(0, 10, 0, "pCylinder1", relative=True)

        # Get its world space transform.
        cubeWorld = cubeChildT3d.inclusiveMatrix()
        cubeWorldList = matrixToList(cubeWorld)

        # Do the same for the equivalent Maya object.
        mayaCubeWorld = cmds.xform(
            "|pCylinder1|pCube1", q=True, ws=True, m=True)

        # Equivalent Maya and USD objects must have the same world transform. 
        assertVectorAlmostEqual(self, cubeWorldList, mayaCubeWorld)

        # Undo to bring scene to its original state.
        for i in range(4):
            cmds.undo()

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)
Beispiel #25
0
    def testParentAbsoluteMultiMatrixOp(self):
        """Test parent -absolute on prim with a transform stack with multiple matrix ops."""

        cmds.file(new=True, force=True)

        # Create a scene with an xform and a capsule.
        import mayaUsd_createStageWithNewLayer

        mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        proxyShapePathStr = '|stage1|stageShape1'
        stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage()
        xformPrim = stage.DefinePrim('/Xform1', 'Xform')
        capsulePrim = stage.DefinePrim('/Capsule1', 'Capsule')
        xformXformable = UsdGeom.Xformable(xformPrim)
        capsuleXformable = UsdGeom.Xformable(capsulePrim)

        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            proxyShapePathStr)

        # Translate and rotate the xform.
        xformPath = ufe.Path(
            [proxyShapePathSegment,
             usdUtils.createUfePathSegment('/Xform1')])
        xformItem = ufe.Hierarchy.createItem(xformPath)

        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(xformItem)

        cmds.move(0, -5, 0, r=True, os=True, wd=True)
        cmds.rotate(0, -90, 0, r=True, os=True, fo=True)

        self.assertEqual(
            xformXformable.GetXformOpOrderAttr().Get(),
            Vt.TokenArray(("xformOp:translate", "xformOp:rotateXYZ")))

        sn.clear()

        capsulePath = ufe.Path([
            proxyShapePathSegment,
            usdUtils.createUfePathSegment('/Capsule1')
        ])
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)

        # Add 3 matrix transform ops to the capsule.
        # matrix A: tx  5, ry 90
        # matrix B: ty 10, rz 90
        # matrix C: tz 15, rx 90
        op = capsuleXformable.AddTransformOp(opSuffix='A')
        matrixValA = Gf.Matrix4d(0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 5, 0, 0,
                                 1)
        op.Set(matrixValA)
        op = capsuleXformable.AddTransformOp(opSuffix='B')
        matrixValB = Gf.Matrix4d(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 10, 0,
                                 1)
        op.Set(matrixValB)
        op = capsuleXformable.AddTransformOp(opSuffix='C')
        matrixValC = Gf.Matrix4d(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 15,
                                 1)
        op.Set(matrixValC)

        matrixOps = [
            "xformOp:transform:A", "xformOp:transform:B", "xformOp:transform:C"
        ]
        matrixValues = {
            "xformOp:transform:A": matrixValA,
            "xformOp:transform:B": matrixValB,
            "xformOp:transform:C": matrixValC
        }

        self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                         Vt.TokenArray(matrixOps))

        # Capture the current world space transform of the capsule.
        capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
        capsuleWorldPre = matrixToList(capsuleT3d.inclusiveMatrix())

        # We will run the parenting test 3 times, targeting each matrix op in
        # turn.
        for matrixOp in matrixOps:
            os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] = matrixOp

            # The xform currently has no children.
            xformHier = ufe.Hierarchy.hierarchy(xformItem)
            xformChildren = xformHier.children()
            self.assertEqual(len(xformChildren), 0)

            # Parent the capsule to the xform.
            cmds.parent(ufe.PathString.string(capsulePath),
                        ufe.PathString.string(xformPath))

            def checkParentDone():
                # The xform now has the capsule as its child.
                xformChildren = xformHier.children()
                self.assertEqual(len(xformChildren), 1)
                self.assertIn('Capsule1', childrenNames(xformChildren))

                # Confirm that the capsule has not moved in world space.  Must
                # re-create the prim and its scene item after path change.
                capsulePath = ufe.Path([
                    proxyShapePathSegment,
                    usdUtils.createUfePathSegment('/Xform1/Capsule1')
                ])
                capsulePrim = mayaUsd.ufe.ufePathToPrim(
                    ufe.PathString.string(capsulePath))
                capsuleXformable = UsdGeom.Xformable(capsulePrim)
                capsuleItem = ufe.Hierarchy.createItem(capsulePath)
                capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
                capsuleWorld = capsuleT3d.inclusiveMatrix()
                assertVectorAlmostEqual(self, capsuleWorldPre,
                                        matrixToList(capsuleWorld))

                # No change in the capsule's transform ops.
                self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                                 Vt.TokenArray(matrixOps))

                # Matrix ops that were not targeted did not change.
                for checkMatrixOp in matrixOps:
                    if checkMatrixOp == matrixOp:
                        continue
                    op = UsdGeom.XformOp(
                        capsulePrim.GetAttribute(checkMatrixOp))
                    self.assertEqual(
                        op.GetOpTransform(
                            mayaUsd.ufe.getTime(
                                ufe.PathString.string(capsulePath))),
                        matrixValues[checkMatrixOp])

            checkParentDone()

            # Undo: the xform no longer has a child, the capsule is still where
            # it has always been.
            cmds.undo()

            xformChildren = xformHier.children()
            self.assertEqual(len(xformChildren), 0)

            capsulePath = ufe.Path([
                proxyShapePathSegment,
                usdUtils.createUfePathSegment('/Capsule1')
            ])
            capsulePrim = mayaUsd.ufe.ufePathToPrim(
                ufe.PathString.string(capsulePath))
            capsuleXformable = UsdGeom.Xformable(capsulePrim)
            capsuleItem = ufe.Hierarchy.createItem(capsulePath)
            capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
            capsuleWorld = capsuleT3d.inclusiveMatrix()
            assertVectorAlmostEqual(self, capsuleWorldPre,
                                    matrixToList(capsuleWorld))
            self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                             Vt.TokenArray(matrixOps))

            # Redo: capsule still hasn't moved.
            cmds.redo()

            checkParentDone()

            # Go back to initial conditions for next iteration of loop.
            cmds.undo()
Beispiel #26
0
    def testParentToProxyShape(self):

        # Load a file with a USD hierarchy at least 2-levels deep.
        with OpenFileCtx("simpleHierarchy.ma"):

            # Create scene items for the proxy shape and the sphere.
            shapeSegment = mayaUtils.createUfePathSegment(
                "|mayaUsdProxy1|mayaUsdProxyShape1")
            shapePath = ufe.Path([shapeSegment])
            shapeItem = ufe.Hierarchy.createItem(shapePath)

            spherePath = ufe.Path(
                [shapeSegment, usdUtils.createUfePathSegment("/pCylinder1/pCube1/pSphere1")])
            sphereItem = ufe.Hierarchy.createItem(spherePath)

            # get the USD stage
            stage = mayaUsd.ufe.getStage(str(shapeSegment))

            # check GetLayerStack behavior
            self.assertEqual(stage.GetEditTarget().GetLayer(), stage.GetRootLayer())

            # The sphere is not a child of the proxy shape.
            shapeHier = ufe.Hierarchy.hierarchy(shapeItem)
            shapeChildren = shapeHier.children()
            self.assertNotIn("pSphere1", childrenNames(shapeChildren))

            # Get its world space transform.
            sphereT3d = ufe.Transform3d.transform3d(sphereItem)
            sphereWorld = sphereT3d.inclusiveMatrix()
            sphereWorldListPre = matrixToList(sphereWorld)

            # Parent sphere to proxy shape in absolute mode (default), using UFE
            # path strings.Expect the exception happens
            cmds.parent("|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1/pSphere1", "|mayaUsdProxy1|mayaUsdProxyShape1")

            # Confirm that the sphere is now a child of the proxy shape.
            shapeChildren = shapeHier.children()
            self.assertIn("pSphere1", childrenNames(shapeChildren))

            # Undo: the sphere is no longer a child of the proxy shape.
            cmds.undo()

            shapeChildren = shapeHier.children()
            self.assertNotIn("pSphere1", childrenNames(shapeChildren))

            # Redo: confirm that the sphere is again a child of the proxy shape.
            cmds.redo()

            shapeChildren = shapeHier.children()
            self.assertIn("pSphere1", childrenNames(shapeChildren))

            # Confirm that the sphere's world transform has not changed.  Must
            # re-create the item, as its path has changed.
            sphereChildPath = ufe.Path(
                [shapeSegment, usdUtils.createUfePathSegment("/pSphere1")])
            sphereChildItem = ufe.Hierarchy.createItem(sphereChildPath)
            sphereChildT3d = ufe.Transform3d.transform3d(sphereChildItem)

            sphereWorld = sphereChildT3d.inclusiveMatrix()
            assertVectorAlmostEqual(
                self, sphereWorldListPre, matrixToList(sphereWorld), places=6)

            # Undo.
            cmds.undo()

            shapeChildren = shapeHier.children()
            self.assertNotIn("pSphere1", childrenNames(shapeChildren))
Beispiel #27
0
    def testParentAbsolute(self):
        # Create scene items for the cube and the cylinder.
        shapeSegment = mayaUtils.createUfePathSegment(
            "|mayaUsdProxy1|mayaUsdProxyShape1")
        cubePath = ufe.Path(
            [shapeSegment, usdUtils.createUfePathSegment("/cubeXform")])
        cubeItem = ufe.Hierarchy.createItem(cubePath)
        cylinderPath = ufe.Path(
            [shapeSegment, usdUtils.createUfePathSegment("/cylinderXform")])
        cylinderItem = ufe.Hierarchy.createItem(cylinderPath)

        # get the USD stage
        stage = mayaUsd.ufe.getStage(str(shapeSegment))

        # check GetLayerStack behavior
        self.assertEqual(stage.GetEditTarget().GetLayer(), stage.GetRootLayer())

        # The cube is not a child of the cylinder.
        cylHier = ufe.Hierarchy.hierarchy(cylinderItem)
        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)

        # Get its world space transform.
        cubeT3d = ufe.Transform3d.transform3d(cubeItem)
        cubeWorld = cubeT3d.inclusiveMatrix()
        cubeWorldListPre = matrixToList(cubeWorld)

        # Parent cube to cylinder in absolute mode (default), using UFE
        # path strings.
        cmds.parent("|mayaUsdProxy1|mayaUsdProxyShape1,/cubeXform",
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/cylinderXform")

        # Confirm that the cube is now a child of the cylinder.
        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 2)
        self.assertIn("cubeXform", childrenNames(cylChildren))

        # Undo: the cube is no longer a child of the cylinder.
        cmds.undo()

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)

        # Redo: confirm that the cube is again a child of the cylinder.
        cmds.redo()

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 2)
        self.assertIn("cubeXform", childrenNames(cylChildren))

        # Confirm that the cube's world transform has not changed.  Must
        # re-create the item, as its path has changed.
        cubeChildPath = ufe.Path(
            [shapeSegment, usdUtils.createUfePathSegment("/cylinderXform/cubeXform")])
        cubeChildItem = ufe.Hierarchy.createItem(cubeChildPath)
        cubeChildT3d = ufe.Transform3d.transform3d(cubeChildItem)

        cubeWorld = cubeChildT3d.inclusiveMatrix()
        assertVectorAlmostEqual(
            self, cubeWorldListPre, matrixToList(cubeWorld), places=6)

        # Cube world y coordinate is currently 0.
        self.assertAlmostEqual(cubeWorld.matrix[3][1], 0)

        # Move the parent
        ufe.GlobalSelection.get().append(cylinderItem)

        cmds.move(0, 10, 0, relative=True)

        # Get the world space transform again.  The y component should
        # have incremented by 10.
        cubeWorld = cubeChildT3d.inclusiveMatrix()
        self.assertAlmostEqual(cubeWorld.matrix[3][1], 10)

        # Undo everything.
        for i in range(2):
            cmds.undo()

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)
Beispiel #28
0
    def testBoundingBox(self):
        '''Test the Object3d bounding box interface.'''

        # Create a simple USD scene.  Default sphere radius is 1, so extents
        # are from (-1, -1, -1) to (1, 1, 1).
        usdFilePath = cmds.internalVar(utd=1) + '/testObject3d.usda'
        stage = Usd.Stage.CreateNew(usdFilePath)
        xform = stage.DefinePrim('/parent', 'Xform')
        sphere = stage.DefinePrim('/parent/sphere', 'Sphere')
        extentAttr = sphere.GetAttribute('extent')
        extent = extentAttr.Get()
        
        assertVectorAlmostEqual(self, extent[0], [-1]*3)
        assertVectorAlmostEqual(self, extent[1], [1]*3)

        # Move the sphere.  Its UFE bounding box should not be affected by
        # transformation hierarchy.
        UsdGeom.XformCommonAPI(xform).SetTranslate((7, 8, 9))

        # Save out the file, and bring it back into Maya under a proxy shape.
        stage.GetRootLayer().Save()

        proxyShape = cmds.createNode('mayaUsdProxyShape')
        cmds.setAttr('mayaUsdProxyShape1.filePath', usdFilePath, type='string')

        # MAYA-101766: loading a stage is done by the proxy shape compute.
        # Because we're a script, no redraw is done, so need to pull explicitly
        # on the outStageData (and simply discard the returned data), to get
        # the proxy shape compute to run, and thus the stage to load.  This
        # should be improved.  Without a loaded stage, UFE item creation
        # fails.  PPT, 31-10-2019.
        outStageData = nameToPlug('mayaUsdProxyShape1.outStageData')
        outStageData.asMDataHandle()

        proxyShapeMayaPath = cmds.ls(proxyShape, long=True)[0]
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            proxyShapeMayaPath)
        
        #######
        # Create a UFE scene item from the sphere prim.
        spherePathSegment = usdUtils.createUfePathSegment('/parent/sphere')
        spherePath = ufe.Path([proxyShapePathSegment, spherePathSegment])
        sphereItem = ufe.Hierarchy.createItem(spherePath)

        # Get its Object3d interface.
        object3d = ufe.Object3d.object3d(sphereItem)

        # Get its bounding box.
        ufeBBox = object3d.boundingBox()

        # Compare it to known extents.
        assertVectorAlmostEqual(self, ufeBBox.min.vector, [-1]*3)
        assertVectorAlmostEqual(self, ufeBBox.max.vector, [1]*3)

        #######
        # Create a UFE scene item from the parent Xform of the sphere prim.
        parentPathSegment = usdUtils.createUfePathSegment('/parent')
        parentPath = ufe.Path([proxyShapePathSegment, parentPathSegment])
        parentItem = ufe.Hierarchy.createItem(parentPath)

        # Get its Object3d interface.
        parentObject3d = ufe.Object3d.object3d(parentItem)

        # Get its bounding box.
        parentUFEBBox = parentObject3d.boundingBox()

        # Compare it to sphere's extents.
        assertVectorEqual(self, ufeBBox.min.vector, parentUFEBBox.min.vector)
        assertVectorEqual(self, ufeBBox.max.vector, parentUFEBBox.max.vector)


        #######
        # Remove the test file.
        os.remove(usdFilePath)
Beispiel #29
0
 def assertVectorAlmostEqual(self, ufeVector, usdVector):
     testUtils.assertVectorAlmostEqual(self, ufeVector.vector, usdVector)
Beispiel #30
0
    def testMultiSelectScaleUSD(self):
        '''Scale multiple USD objects, read through Transform3d interface.'''

        # Select multiple balls to scale them.
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        # Test passes for a single item.
        # balls = ['Ball_33']
        balls = ['Ball_33', 'Ball_34']
        ballPaths = [
            ufe.Path([
                proxyShapePathSegment,
                usdUtils.createUfePathSegment('/Room_set/Props/' + ball)
            ]) for ball in balls
        ]
        ballItems = [
            ufe.Hierarchy.createItem(ballPath) for ballPath in ballPaths
        ]

        for ballItem in ballItems:
            ufe.GlobalSelection.get().append(ballItem)

        # We compare the UFE scale with the USD run-time scale.  To
        # obtain the full scale of USD scene items, we need to add the USD
        # scale to the Maya proxy shape scale.
        proxyShapeXformObj = om.MSelectionList().add('transform1').getDagPath(
            0).node()
        proxyShapeXformFn = om.MFnTransform(proxyShapeXformObj)

        def usdSceneItemScale(item):
            prim = usdUtils.getPrimFromSceneItem(item)
            if not prim.HasAttribute('xformOp:scale'):
                return proxyShapeXformFn.scale()
            else:
                return combineScales(proxyShapeXformFn.scale(),
                                     prim.GetAttribute('xformOp:scale').Get())

        def ufeSceneItemScale(item):
            return transform3dScale(ufe.Transform3d.transform3d(item))

        # Set up the callables that will retrieve the scale.
        self.runTimeScale = usdSceneItemScale
        self.ufeScale = ufeSceneItemScale

        # Give the tail item in the selection an initial scale that
        # is different, to catch bugs where the relative scale
        # incorrectly squashes any existing scale.
        backItem = ballItems[-1]
        backT3d = ufe.Transform3d.transform3d(backItem)
        initialScale = [1.1, 2.2, 3.3]
        backT3d.scale(*initialScale)
        assertVectorAlmostEqual(self,
                                initialScale,
                                usdSceneItemScale(backItem),
                                places=6)

        # Save the initial positions to the memento list.
        expected = [usdSceneItemScale(ballItem) for ballItem in ballItems]

        self.runMultiSelectTestScale(ballItems, expected, places=6)