예제 #1
0
    def testMultiSelectMoveUSD(self):
        '''Move multiple USD objects, read through Transform3d interface.'''

        # Select multiple balls to move them.
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            "|world|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)
예제 #2
0
    def _testMultiSelectRotateUSD(self):
        '''Rotate multiple USD objects, read through Transform3d interface.'''

        # Select multiple balls to rotate them.
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            "|world|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 rotation with the USD run-time rotation.  To
        # obtain the full rotation of USD scene items, we need to add the USD
        # rotation to the Maya proxy shape rotation.
        proxyShapeXformObj = om.MSelectionList().add('transform1').getDagPath(0).node()
        proxyShapeXformFn = om.MFnTransform(proxyShapeXformObj)

        def usdSceneItemRotation(item):
            prim = usdUtils.getPrimFromSceneItem(item)
            if not prim.HasAttribute('xformOp:rotateXYZ'):
                return proxyShapeXformFn.rotation(om.MSpace.kTransform)
            else:
                x,y,z = prim.GetAttribute('xformOp:rotateXYZ').Get()
                return  proxyShapeXformFn.rotation(om.MSpace.kTransform) + om.MEulerRotation(radians(x), radians(y), radians(z))

        def ufeSceneItemRotation(item):
            return transform3dRotation(ufe.Transform3d.transform3d(item))

        # Set up the callables that will retrieve the rotation.
        self.runTimeRotation = usdSceneItemRotation
        self.ufeRotation = ufeSceneItemRotation

        # Give the tail item in the selection an initial rotation that
        # is different, to catch bugs where the relative rotation
        # incorrectly squashes any existing rotation.
        backItem = ballItems[-1]
        backT3d = ufe.Transform3d.transform3d(backItem)
        initialRot = [-10, -20, -30]
        backT3d.rotate(*initialRot)
        assertVectorAlmostEqual(self, [radians(a) for a in initialRot], 
                                     usdSceneItemRotation(backItem))

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

        # MAYA-96058: unfortunately, rotate command currently requires a rotate
        # manipulator to be created to update the UFE object.
        manipCtx = cmds.manipRotateContext()
        cmds.setToolTo(manipCtx)

        #Temporarily disabling undo redo until we fix it for PR 94
        self.runMultiSelectTestRotate(ballItems, expected)
예제 #3
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)
예제 #4
0
    def testAnimatedBoundingBox(self):
        '''Test the Object3d bounding box interface for animated geometry.'''

        # Load up a scene with a sphere that has an animated radius, with
        # time connected to the proxy shape.
        filePath = os.path.join(os.path.dirname(os.path.realpath(__file__)),
                                "test-samples", "sphereAnimatedRadius",
                                "sphereAnimatedRadiusProxyShape.ma")

        cmds.file(filePath, force=True, open=True)

        # 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(
            '|world|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 xrange(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)
예제 #5
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)
예제 #6
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)
예제 #7
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)
예제 #8
0
    def testMultiSelectScaleUSD(self):
        '''Scale multiple USD objects, read through Transform3d interface.'''

        # Select multiple balls to scale them.
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            "|world|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)
예제 #9
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
예제 #10
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)
예제 #11
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
예제 #12
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)

        # Remove the test file.
        os.remove(usdFilePath)
예제 #13
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)
예제 #14
0
    def testParentRelative(self):
        # Create scene items for the cube and the cylinder.
        shapeSegment = mayaUtils.createUfePathSegment(
            "|world|mayaUsdProxy1|mayaUsdProxyShape1")
        cubePath = ufe.Path(
            [shapeSegment,
             usdUtils.createUfePathSegment("/pCube1")])
        cubeItem = ufe.Hierarchy.createItem(cubePath)
        cylinderPath = ufe.Path(
            [shapeSegment,
             usdUtils.createUfePathSegment("/pCylinder1")])
        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), 0)

        # 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,/pCube1",
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1",
                    relative=True)

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

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

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

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

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)
        self.assertIn("pCube1", 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("/pCylinder1/pCube1")
        ])
        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), 0)
예제 #15
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(
                "|world|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))
예제 #16
0
    def testParentAbsolute(self):
        # Create scene items for the cube and the cylinder.
        shapeSegment = mayaUtils.createUfePathSegment(
            "|world|mayaUsdProxy1|mayaUsdProxyShape1")
        cubePath = ufe.Path(
            [shapeSegment,
             usdUtils.createUfePathSegment("/pCube1")])
        cubeItem = ufe.Hierarchy.createItem(cubePath)
        cylinderPath = ufe.Path(
            [shapeSegment,
             usdUtils.createUfePathSegment("/pCylinder1")])
        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), 0)

        # 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,/pCube1",
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1")

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

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

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

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

        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 1)
        self.assertIn("pCube1", 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("/pCylinder1/pCube1")
        ])
        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), 0)