예제 #1
0
class SelectTestCase(unittest.TestCase):
    '''Verify UFE selection on a USD scene.'''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        cmds.file(new=True, force=True)

        standalone.uninitialize()

    def setUp(self):
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Load a file that has the same scene in both the Maya Dag
        # hierarchy and the USD hierarchy.
        mayaUtils.openTestScene("parentCmd", "simpleSceneMayaPlusUSD_TRS.ma")

        # Create multiple scene items.  We will alternate between selecting a
        # Maya item and a USD item, 3 items each data model, one item at a
        # time, so we select 6 different items, one at a time.
        shapeSegment = mayaUtils.createUfePathSegment(
            "|mayaUsdProxy1|mayaUsdProxyShape1")
        ufeNames = ["cubeXform", "cylinderXform", "sphereXform"]
        mayaNames = ["pCube1", "pCylinder1", "pSphere1"]
        usdPaths = []
        for n in ["/" + o for o in ufeNames]:
            usdPaths.append(
                ufe.Path([shapeSegment,
                          usdUtils.createUfePathSegment(n)]))
        mayaPaths = []
        for n in ["|" + o for o in mayaNames]:
            mayaPaths.append(ufe.Path(mayaUtils.createUfePathSegment(n)))

        # Create a list of paths by alternating USD objects and Maya objects
        # Flatten zipped tuples using list comprehension double loop.
        zipped = zip(usdPaths, mayaPaths)
        paths = [j for i in zipped for j in i]

        # Create items for all paths.
        self.items = [ufe.Hierarchy.createItem(p) for p in paths]

        # Clear selection to start off
        cmds.select(clear=True)

    def runTestSelection(self, selectCmd):
        '''Run the replace selection test, using the argument command to
        replace the selection with a single scene item.'''

        # Clear the selection.
        globalSn = ufe.GlobalSelection.get()
        globalSn.clear()
        self.assertTrue(globalSn.empty())

        # Select all items in turn.
        for item in self.items:
            selectCmd(item)

        # Item in the selection should be the last item in our list.
        self.assertEqual(len(globalSn), 1)

        def snFront(sn):
            return next(iter(sn)) if \
                ufe.VersionInfo.getMajorVersion() == 1 else sn.front()

        self.assertEqual(snFront(globalSn), self.items[-1])

        # Check undo.  For this purpose, re-create the list of items in reverse
        # order.  Because we're already at the last item, we skip the last one
        # (i.e. last item is -1, so start at -2, and increment by -1).
        rItems = self.items[-2::-1]

        # Undo until the first element, checking the selection as we go.
        for i in rItems:
            cmds.undo()
            self.assertEqual(len(globalSn), 1)
            self.assertEqual(snFront(globalSn), i)

        # Check redo.
        fItems = self.items[1:]

        # Redo until the last element, checking the selection as we go.
        for i in fItems:
            cmds.redo()
            self.assertEqual(len(globalSn), 1)
            self.assertEqual(snFront(globalSn), i)

    def testUfeSelect(self):
        def selectCmd(item):
            sn = ufe.Selection()
            sn.append(item)
            ufeSelectCmd.replaceWith(sn)

        self.runTestSelection(selectCmd)

    @unittest.skipUnless(ufeUtils.ufeFeatureSetVersion() >= 2,
                         'testMayaSelect only available in UFE v2 or greater.')
    def testMayaSelect(self):
        # Maya PR 121 now has support for UFE path string in select command.
        def selectCmd(item):
            cmds.select(ufe.PathString.string(item.path()))

        self.runTestSelection(selectCmd)

    @unittest.skipUnless(
        ufeUtils.ufeFeatureSetVersion() >= 2,
        'testMayaSelectFlags only available in UFE v2 or greater.')
    def testMayaSelectFlags(self):
        # Maya PR 121 now has support for UFE path string in select command.

        # Clear the selection.
        globalSn = ufe.GlobalSelection.get()
        globalSn.clear()
        self.assertTrue(globalSn.empty())

        # Incrementally add to the selection all items in turn.
        # Also testing undo/redo along the way.
        cnt = 0
        for item in self.items:
            cnt += 1
            cmds.select(ufe.PathString.string(item.path()), add=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt - 1, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

        # Since we added all the items to the global selection, it
        # should have all of them.
        self.assertEqual(len(globalSn), len(self.items))

        # Ensure the global selection order is the same as our item order.
        itemIt = iter(self.items)
        for selIt in globalSn:
            self.assertEqual(selIt, next(itemIt))

        # Incrementally remove from the selection all items in turn.
        # Also testing undo/redo along the way.
        for item in self.items:
            cnt -= 1
            cmds.select(ufe.PathString.string(item.path()), deselect=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt + 1, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

        # Since we removed all items from global selection it
        # should be empty now.
        self.assertTrue(globalSn.empty())

        # Incrementally toggle selection state of all items in turn.
        # Since they all start unselected, they will toggle to selected.
        # Also testing undo/redo along the way.
        globalSn.clear()
        self.assertTrue(globalSn.empty())
        cnt = 0
        for item in self.items:
            cnt += 1
            cmds.select(ufe.PathString.string(item.path()), toggle=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt - 1, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

        # Since we toggled each item to selected, we should have all
        # of them on the global selection.
        self.assertEqual(len(globalSn), len(self.items))

        # Incrementally toggle selection state of all items in turn.
        # Since they all start selected, they will toggle to unselected.
        # Also testing undo/redo along the way.
        for item in self.items:
            cnt -= 1
            cmds.select(ufe.PathString.string(item.path()), toggle=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt + 1, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

        # Since we toggled all items to unselected, the global selection
        # should be empty now.
        self.assertTrue(globalSn.empty())

        # Select all the items at once, replacing the existing selection.
        # Also testing undo/redo.
        pathStrings = [ufe.PathString.string(i.path()) for i in self.items]
        cmds.select(*pathStrings, replace=True)
        self.assertEqual(len(globalSn), len(self.items))

        # Ensure the global selection order is the same as our item order.
        itemIt = iter(self.items)
        for selIt in globalSn:
            self.assertEqual(selIt, next(itemIt))

        cmds.undo()
        self.assertEqual(0, len(globalSn))
        self.assertTrue(globalSn.empty())

        cmds.redo()
        self.assertEqual(len(globalSn), len(self.items))

        # Ensure the global selection order is the same as our item order.
        itemIt = iter(self.items)
        for selIt in globalSn:
            self.assertEqual(selIt, next(itemIt))

        # With all items selected (and in same order as item order)
        # "select -add" the first item which will move it to the end.
        first = self.items[0]
        self.assertEqual(globalSn.front(), first)
        cmds.select(ufe.PathString.string(first.path()), add=True)
        self.assertTrue(globalSn.contains(first.path()))
        self.assertEqual(globalSn.back(), first)

    @unittest.skipUnless((
        (ufeUtils.ufeFeatureSetVersion() >= 2) and
        (mayaUtils.previewReleaseVersion() >= 123)
    ), 'testMayaSelectMuteLayer only available in UFE v2 or greater and Maya Preview Release 123 or later.'
                         )
    def testMayaSelectMuteLayer(self):
        '''Stale selection items must be removed on mute layer.'''

        # Create new stage
        import mayaUsd_createStageWithNewLayer
        proxyShapePathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer(
        )
        proxyShapePath = ufe.PathString.path(proxyShapePathStr)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)

        # Create a sub-layer.
        stage = mayaUsd.lib.GetPrim(proxyShapePathStr).GetStage()
        rootLayer = stage.GetRootLayer()
        subLayerId = cmds.mayaUsdLayerEditor(rootLayer.identifier,
                                             edit=True,
                                             addAnonymous="Layer1")[0]

        # Set the edit target to new sub-layer.
        cmds.mayaUsdEditTarget(proxyShapePathStr,
                               edit=True,
                               editTarget=subLayerId)

        # Create a prim.  This will create the primSpec in the new sub-layer.
        proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])

        capsulePathStr = '%s,/Capsule1' % proxyShapePathStr
        capsulePath = ufe.PathString.path(capsulePathStr)
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)

        # Select the prim.  This is the core of the test: on subtree invalidate,
        # the prim's UFE scene item should be removed from the selection.
        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(capsuleItem)

        self.assertTrue(sn.contains(capsulePath))

        # Mute sub-layer
        cmds.mayaUsdLayerEditor(subLayerId,
                                edit=True,
                                muteLayer=[1, proxyShapePathStr])

        # Should be nothing on the selection list.
        self.assertTrue(sn.empty())

        # Undo: capsule should be back on the selection list.
        cmds.undo()

        self.assertTrue(sn.contains(capsulePath))

        # Redo: selection list now empty.
        cmds.redo()

        self.assertTrue(sn.empty())

        cmds.undo()

        # Change attribute on the capsule, using the item from the selection,
        # which must be valid.
        self.assertTrue(len(sn), 1)
        capsuleItem = sn.front()
        capsuleAttrs = ufe.Attributes.attributes(capsuleItem)
        self.assertIsNotNone(capsuleAttrs)

        capsuleRadius = capsuleAttrs.attribute('radius')
        capsuleRadius.set(2)

        self.assertEqual(capsuleRadius.get(), 2)

        # Now mute the layer outside a Maya command.  Stale scene items must be
        # removed from the selection.
        self.assertTrue(len(sn), 1)
        self.assertTrue(sn.contains(capsulePath))

        stage.MuteLayer(subLayerId)

        self.assertTrue(sn.empty())

    @unittest.skipUnless((
        (ufeUtils.ufeFeatureSetVersion() >= 2) and
        (mayaUtils.previewReleaseVersion() >= 123)
    ), 'testMayaSelectSwitchVariant only available in UFE v2 or greater and Maya Preview Release 123 or later.'
                         )
    def testMayaSelectSwitchVariant(self):
        '''Stale selection items must be removed on variant switch.'''

        import mayaUsd_createStageWithNewLayer
        import maya.internal.ufeSupport.ufeCmdWrapper as ufeCmd

        # Create a scene with two variants.
        proxyShape = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        stage = mayaUsd.lib.GetPrim(proxyShape).GetStage()
        top = stage.DefinePrim('/Xform1', 'Xform')
        vset = top.GetVariantSets().AddVariantSet('modelingVariant')
        vset.AddVariant('cube')
        vset.AddVariant('sphere')
        vset.SetVariantSelection('cube')
        with vset.GetVariantEditContext():
            stage.DefinePrim('/Xform1/Cube', 'Cube')
        vset.SetVariantSelection('sphere')
        with vset.GetVariantEditContext():
            stage.DefinePrim('/Xform1/Sphere', 'Sphere')

        # The sphere is the sole child of Xform1.  Get an attribute from it,
        # select it.
        xformPath = ufe.PathString.path('%s,/Xform1' % proxyShape)
        spherePath = ufe.PathString.path('%s,/Xform1/Sphere' % proxyShape)
        xformItem = ufe.Hierarchy.createItem(xformPath)
        sphereItem = ufe.Hierarchy.createItem(spherePath)

        xformHier = ufe.Hierarchy.hierarchy(xformItem)
        xformChildren = xformHier.children()
        self.assertEqual(len(xformChildren), 1)
        self.assertEqual(xformChildren[0].path(), spherePath)
        sphereAttrs = ufe.Attributes.attributes(sphereItem)
        sphereRadius = sphereAttrs.attribute('radius')
        self.assertEqual(sphereRadius.get(), 1)

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

        self.assertEqual(len(sn), 1)

        # Switch variants using a command: the cube is now the sole child of
        # Xform1, we can get an attribute from the cube.  The selection must
        # now be empty.
        xformCtxOps = ufe.ContextOps.contextOps(xformItem)
        cmd = xformCtxOps.doOpCmd(['Variant Sets', 'modelingVariant', 'cube'])
        ufeCmd.execute(cmd)

        cubePath = ufe.PathString.path('%s,/Xform1/Cube' % proxyShape)
        cubeItem = ufe.Hierarchy.createItem(cubePath)

        xformChildren = xformHier.children()
        self.assertEqual(len(xformChildren), 1)
        self.assertEqual(xformChildren[0].path(), cubePath)
        cubeAttrs = ufe.Attributes.attributes(cubeItem)
        cubeRadius = cubeAttrs.attribute('size')
        self.assertEqual(cubeRadius.get(), 2)

        self.assertTrue(sn.empty())

        # Undo: selection is restored, seletion item is valid.
        cmds.undo()

        self.assertEqual(len(sn), 1)
        sphereItem = sn.front()
        self.assertEqual(sphereItem.path(), spherePath)
        sphereAttrs = ufe.Attributes.attributes(sphereItem)
        sphereRadius = sphereAttrs.attribute('radius')
        self.assertEqual(sphereRadius.get(), 1)
        xformChildren = xformHier.children()
        self.assertEqual(len(xformChildren), 1)
        self.assertEqual(xformChildren[0].path(), spherePath)

        # Redo: selection is cleared.
        cmds.redo()

        self.assertTrue(sn.empty())
        xformChildren = xformHier.children()
        self.assertEqual(len(xformChildren), 1)
        self.assertEqual(xformChildren[0].path(), cubePath)

        # Undo: selection restored to sphere.
        cmds.undo()

        self.assertEqual(len(sn), 1)
        sphereItem = sn.front()
        self.assertEqual(sphereItem.path(), spherePath)

        # Now set the variant outside a Maya command.  Stale scene items must be
        # removed from the selection.
        vset.SetVariantSelection('cube')

        self.assertTrue(sn.empty())

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 126,
        'Requires Maya fixes only available in Maya Preview Release 126 or later.'
    )
    def testMayaSelectUndoPrimCreation(self):
        '''Test if the SceneItem's prim is still valid on selection after the prim creation is undone then redone'''

        # helper function to check if the current
        # selected SceneItem's prim is valid.
        def checkSelectedSceneItemPrim(expectedSceneItem):
            globalSn = ufe.GlobalSelection.get()
            self.assertEqual(len(globalSn), 1)
            sceneItem = globalSn.front()
            self.assertEqual(sceneItem, expectedSceneItem)
            prim = mayaUsd.ufe.getPrimFromRawItem(sceneItem.getRawAddress())
            self.assertTrue(prim)

        shapeNode, shapeStage = mayaUtils.createProxyAndStage()
        proxyShapePath = ufe.PathString.path(shapeNode)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)

        with mayaUsd.lib.UsdUndoBlock():
            proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])

        with mayaUsd.lib.UsdUndoBlock():
            proxyShapeContextOps.doOp(['Add New Prim', 'Cube'])

        capsulePath = ufe.PathString.path('%s,/Capsule1' % shapeNode)
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)
        cmds.select(ufe.PathString.string(capsuleItem.path()), replace=True)
        checkSelectedSceneItemPrim(capsuleItem)

        cubePath = ufe.PathString.path('%s,/Cube1' % shapeNode)
        cubeItem = ufe.Hierarchy.createItem(cubePath)
        cmds.select(ufe.PathString.string(cubeItem.path()), replace=True)
        checkSelectedSceneItemPrim(cubeItem)

        cmds.undo()  # undo the selection of the cube
        cmds.undo()  # undo the selection of the capsule
        cmds.undo()  # undo the creation of the cube
        cmds.undo()  # undo the creation of the capsule

        cmds.redo()  # redo the creation of the capsule
        cmds.redo()  # redo the creation of the cube
        cmds.redo()  # redo the selection of the capsule
        checkSelectedSceneItemPrim(capsuleItem)
        cmds.redo()  # redo the selection of the cube
        checkSelectedSceneItemPrim(cubeItem)
        cmds.undo()  # undo the selection of the cube
        checkSelectedSceneItemPrim(capsuleItem)
예제 #2
0
class DuplicateCmdTestCase(unittest.TestCase):
    '''Verify the Maya delete command, for multiple runtimes.

    UFE Feature : SceneItemOps
    Maya Feature : duplicate
    Action : Duplicate objects in the scene.
    Applied On Selection :
        - Multiple Selection [Mixed, Non-Maya].  Maya-only selection tested by
          Maya.
    Undo/Redo Test : Yes
    Expect Results To Test :
        - Duplicate objects in the scene.
    Edge Cases :
        - None.
    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    def setUp(self):
        ''' Called initially to set up the Maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Open top_layer.ma scene in testSamples
        mayaUtils.openTopLayerScene()

        # Create some extra Maya nodes
        cmds.polySphere()

        # Clear selection to start off
        cmds.select(clear=True)

    def testDuplicate(self):
        '''Duplicate Maya and USD objects.'''

        # Select two objects, one Maya, one USD.
        spherePath = ufe.Path(mayaUtils.createUfePathSegment("|pSphere1"))
        sphereItem = ufe.Hierarchy.createItem(spherePath)
        sphereHierarchy = ufe.Hierarchy.hierarchy(sphereItem)
        worldItem = sphereHierarchy.parent()

        ball35Path = ufe.Path([
            mayaUtils.createUfePathSegment(
                "|transform1|proxyShape1"),
            usdUtils.createUfePathSegment("/Room_set/Props/Ball_35")])
        ball35Item = ufe.Hierarchy.createItem(ball35Path)
        ball35Hierarchy = ufe.Hierarchy.hierarchy(ball35Item)
        propsItem = ball35Hierarchy.parent()

        worldHierarchy = ufe.Hierarchy.hierarchy(worldItem)
        worldChildrenPre = worldHierarchy.children()
        propsHierarchy = ufe.Hierarchy.hierarchy(propsItem)
        propsChildrenPre = propsHierarchy.children()

        ufe.GlobalSelection.get().append(sphereItem)
        ufe.GlobalSelection.get().append(ball35Item)

        # Set the edit target to the layer in which Ball_35 is defined (has a
        # primSpec, in USD terminology).  Otherwise, duplication will not find
        # a source primSpec to copy.  Layers are the (anonymous) session layer,
        # the root layer, then the Assembly_room_set sublayer.  Trying to find
        # the layer by name is not practical, as it requires the full path
        # name, which potentially differs per run-time environment.
        ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item)
        stage = ball35Prim.GetStage()

        layer = stage.GetLayerStack()[2]
        stage.SetEditTarget(layer)

        cmds.duplicate()

        # The duplicate command doesn't return duplicated non-Maya UFE objects.
        # They are in the selection, in the same order as the sources.
        snIter = iter(ufe.GlobalSelection.get())
        sphereDupItem = next(snIter)
        sphereDupName = str(sphereDupItem.path().back())
        ball35DupItem = next(snIter)
        ball35DupName = str(ball35DupItem.path().back())

        worldChildren = worldHierarchy.children()
        propsChildren = propsHierarchy.children()

        self.assertEqual(len(worldChildren)-len(worldChildrenPre), 1)
        self.assertEqual(len(propsChildren)-len(propsChildrenPre), 1)

        self.assertIn(sphereDupItem, worldChildren)
        self.assertIn(ball35DupItem, propsChildren)

        cmds.undo()

        # The duplicated items should no longer appear in the child list of
        # their parents.

        def childrenNames(children):
            return [str(child.path().back()) for child in children]

        worldHierarchy = ufe.Hierarchy.hierarchy(worldItem)
        worldChildren = worldHierarchy.children()
        propsHierarchy = ufe.Hierarchy.hierarchy(propsItem)
        propsChildren = propsHierarchy.children()

        worldChildrenNames = childrenNames(worldChildren)
        propsChildrenNames = childrenNames(propsChildren)

        self.assertNotIn(sphereDupName, worldChildrenNames)
        self.assertNotIn(ball35DupName, propsChildrenNames)

        # The duplicated items shoudl reappear after a redo
        cmds.redo()

        snIter = iter(ufe.GlobalSelection.get())
        sphereDupItem = next(snIter)
        ball35DupItem = next(snIter)

        worldChildren = worldHierarchy.children()
        propsChildren = propsHierarchy.children()

        self.assertEqual(len(worldChildren)-len(worldChildrenPre), 1)
        self.assertEqual(len(propsChildren)-len(propsChildrenPre), 1)

        self.assertIn(sphereDupItem, worldChildren)
        self.assertIn(ball35DupItem, propsChildren)

        cmds.undo()

        # The duplicated items should not be assigned to the name of a
        # deactivated USD item.

        cmds.select(clear=True)

        # Delete the even numbered props:
        evenPropsChildrenPre = propsChildrenPre[0:35:2]
        for propChild in evenPropsChildrenPre:
            ufe.GlobalSelection.get().append(propChild)
        cmds.delete()

        worldHierarchy = ufe.Hierarchy.hierarchy(worldItem)
        worldChildren = worldHierarchy.children()
        propsHierarchy = ufe.Hierarchy.hierarchy(propsItem)
        propsChildren = propsHierarchy.children()
        propsChildrenPostDel = propsHierarchy.children()

        # Duplicate Ball_1
        ufe.GlobalSelection.get().append(propsChildrenPostDel[0])

        cmds.duplicate()

        snIter = iter(ufe.GlobalSelection.get())
        ballDupItem = next(snIter)
        ballDupName = str(ballDupItem.path().back())

        self.assertNotIn(ballDupItem, propsChildrenPostDel)
        self.assertNotIn(ballDupName, propsChildrenNames)
        self.assertEqual(ballDupName, "Ball_36")

        cmds.undo()  # undo duplication
        cmds.undo()  # undo deletion

    @unittest.skipUnless(mayaUtils.previewReleaseVersion() >= 121, 'Requires Maya fixes only available in Maya Preview Release 121 or later.')
    def testSmartTransformDuplicate(self):
        '''Test smart transform option of duplicate command.'''
        torusFile = testUtils.getTestScene("groupCmd", "torus.usda")
        torusDagPath, torusStage = mayaUtils.createProxyFromFile(torusFile)
        usdTorusPathString = torusDagPath + ",/pTorus1"

        cmds.duplicate(usdTorusPathString)
        cmds.move(10, 0, 0, r=True)
        smartDup = cmds.duplicate(smartTransform=True)

        usdTorusItem = ufeUtils.createUfeSceneItem(torusDagPath, '/pTorus3')
        torusT3d = ufe.Transform3d.transform3d(usdTorusItem)
        transVector = torusT3d.inclusiveMatrix().matrix[-1]

        correctResult = [20, 0, 0, 1]

        self.assertEqual(correctResult, transVector)
예제 #3
0
class ComboCmdTestCase(testTRSBase.TRSTestCaseBase):
    '''Verify the Transform3d UFE interface, for multiple runtimes.

    The Maya move, rotate, and scale commands is used to test setting object
    translation, rotation, and scale.
    As of 05-May-2020, object space relative moves, rotates, and scales are
    supported by Maya code, and move and rotate are supported in world space
    as well, although scale is not supported in world space.
    Object translation, rotation, and scale is read using the Transform3d
    interface and the native run-time interface.

    This test performs a sequence of the possible types of operations, and
    verifies that the position, rotation, and scale of the object has been
    modified according to how such operations should cumulate.

    The expected value consists of the translate, rotate, and scale vectors
    (in world space). It is computed by:
        - initializing the translate, rotate, and scale vectors
        - calling updateTRS after each operation; this method will reassemble
          the transformation matrix from the three vectors, apply the
          appropriate matrix transformation for the given operation, in the
          given space, and extract the translate, rotate, and scale vector,
          once again in world space.

    When a snapshot is taken for comparison purposes, the value extracted from
    the runtime objects is extracted for each component, and assembled into a
    vector that can be compared to the computed expected value vector.
    
    UFE Feature : Transform3d
    Maya Feature : move, rotate, scale
    Action : Relative move, rotate, and scale in object space; move, rotate in
    object space.
    Applied On Selection :
        - No selection - Given node as param
        - Single Selection [Maya, Non-Maya]
    Undo/Redo Test : Yes
    Expect Results To Test :
        - Maya Dag object world space position.
        - USD object world space position.
    Edge Cases :
        - None.
    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.setUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        standalone.uninitialize()

    def setUp(self):
        ''' Called initially to set up the maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Set up memento, a list of snapshots.
        self.memento = []

        # Callables to get current object translation and rotation using the
        # run-time and UFE.
        self.move = 0
        self.rotate = 1
        self.scale = 2
        self.ops = [self.move, self.rotate, self.scale]
        self.runTimes = [None, None, None]
        self.ufes = [None, None, None]

        self.noSpace = None
        self.spaces = [om.MSpace.kObject, om.MSpace.kWorld]

        # Open top_layer.ma scene in testSamples
        mayaUtils.openTopLayerScene()

        # Create some extra Maya nodes
        cmds.polySphere()
        cmds.polyCube()

        # Clear selection to start off
        cmds.select(clear=True)

    def updateTRS(self, expectedTRS, op, v, space=om.MSpace.kWorld):
        '''Update the expected vector based on given operation, vector and space
        The expectedTRS vector has 9 entries:
        * 0-2 The world position
        * 3-5 The world rotation (in degrees)
        * 6-8 The world scale
        The possible operations are move, rotate, and scale.
        The possible spaces are kObject and kWorld (default)
        '''
        if (expectedTRS is None):
            expectedTRS = [None] * 9
        # trs starts as the identity matrix
        #
        trs = om.MTransformationMatrix()

        # Add translation, rotation, and scale, in world space, to recreate
        # the last transformation matrix.
        #
        if (expectedTRS[0] is not None):
            trs.setTranslation(
                om.MVector(expectedTRS[0], expectedTRS[1], expectedTRS[2]),
                om.MSpace.kWorld)
        if (expectedTRS[3] is not None):
            trs.setRotation(
                om.MEulerRotation(radians(expectedTRS[3]),
                                  radians(expectedTRS[4]),
                                  radians(expectedTRS[5])))
        if (expectedTRS[6] is not None):
            trs.setScale(
                om.MVector(expectedTRS[6], expectedTRS[7], expectedTRS[8]),
                om.MSpace.kWorld)
        # Apply the requested operation. If the space is kObject, and we had a
        # scale factor, we must counteract it to get the right matrix, by
        # dividing the translation vector by it (otherwise it ends up being
        # scaled twice, and the expected value is incorrect).
        #
        if op == self.move:
            if (space == om.MSpace.kObject and expectedTRS[6] is not None):
                trs.translateBy(
                    om.MVector(v[0] / expectedTRS[6], v[1] / expectedTRS[7],
                               v[2] / expectedTRS[8]), space)
            else:
                trs.translateBy(om.MVector(v[0], v[1], v[2]), space)
        elif op == self.rotate:
            trs.rotateBy(
                om.MEulerRotation(radians(v[0]), radians(v[1]), radians(v[2])),
                space)
        elif op == self.scale:
            trs.scaleBy(om.MVector(v[0], v[1], v[2]), space)
        # Recover the world space translate, rotate, and scale, and updated
        # the expected vector
        #
        expectedTRS[0:3] = trs.translation(om.MSpace.kWorld)
        r = trs.rotation().asVector()
        expectedTRS[3] = degrees(r[0])
        expectedTRS[4] = degrees(r[1])
        expectedTRS[5] = degrees(r[2])
        expectedTRS[6:9] = trs.scale(om.MSpace.kWorld)
        return expectedTRS

    def extractTRS(self, expectedTRS, op):
        '''Extract the move, rotate, or scale component
        '''
        if op == self.move:
            # Translation (x, y, z)
            #
            return expectedTRS[0:3]
        elif op == self.rotate:
            # Rotation vector in degrees (x, y, z)
            #
            return expectedTRS[3:6]
        elif op == self.scale:
            # Scale (x, y, z)
            #
            return expectedTRS[6:9]

    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)

    def runTestCombo(self, expectedTRS):
        '''Engine method to run move, rotate, and scale test.'''

        # Save the initial values to the memento list.
        self.snapShotAndTest(expectedTRS, 6)

        # Do a combination of commands, and compare with expected.
        # Note: scale not supported in kObject space, hence, no test
        #       rotate values are in degrees
        #
        ops = [[self.rotate, [10, 20, 30], om.MSpace.kObject],
               [self.move, [4, 5, 6], om.MSpace.kWorld],
               [self.move, [4, 5, 6], om.MSpace.kObject],
               [self.scale, [.1, 10, 100], om.MSpace.kObject],
               [self.rotate, [-10, -20, -30], om.MSpace.kWorld],
               [self.move, [-3, -2, -1], om.MSpace.kWorld],
               [self.scale, [1000, .01, .1], om.MSpace.kObject],
               [self.move, [-3, -2, -1], om.MSpace.kObject]]
        for item in ops:
            op = item[0]
            if (op not in self.ops):
                continue
            v = item[1]
            space = item[2]
            if (op == self.move):
                if (space == om.MSpace.kObject):
                    cmds.move(v[0],
                              v[1],
                              v[2],
                              relative=True,
                              os=True,
                              wd=True)
                else:
                    cmds.move(v[0], v[1], v[2], relative=True)
            elif (op == self.rotate):
                if (space == om.MSpace.kObject):
                    cmds.rotate(v[0],
                                v[1],
                                v[2],
                                relative=True,
                                os=True,
                                forceOrderXYZ=True)
                else:
                    cmds.rotate(v[0],
                                v[1],
                                v[2],
                                relative=True,
                                ws=True,
                                forceOrderXYZ=True)
            elif (op == self.scale):
                if (space == om.MSpace.kObject):
                    cmds.scale(v[0], v[1], v[2], relative=True)
                else:
                    # scale is only supported in object space; if it is
                    # eventually supported in world space, this would be the
                    # command to emit:
                    #cmds.scale(v[0], v[1], v[2], relative=True, ws=True)
                    # Fail if we attempt to test this type of operation
                    self.assertEqual(space, om.MSpace.kObject,
                                     'scale only supported in object space')
                    continue
            expectedTRS = self.updateTRS(expectedTRS, op, v, space)

            self.snapShotAndTest(expectedTRS, 6)

        # Test undo, redo.
        self.rewindMemento()
        self.fforwardMemento()

    def testComboMaya(self):
        '''Move, rotate, and scale Maya object, read through the Transform3d interface.'''
        # Give the sphere an initial position, rotation, scale, and select it.
        sphereObj = om.MSelectionList().add('pSphere1').getDagPath(0).node()
        sphereFn = om.MFnTransform(sphereObj)

        expectedTRS = None
        if (self.move in self.ops):
            expectedTRS = self.updateTRS(expectedTRS, self.move, [1, 2, 3])
            t = self.extractTRS(expectedTRS, self.move)
            sphereFn.setTranslation(om.MVector(t[0], t[1], t[2]),
                                    om.MSpace.kTransform)
        if (self.rotate in self.ops):
            expectedTRS = self.updateTRS(expectedTRS, self.rotate,
                                         [30, 60, 90])
            r = self.extractTRS(expectedTRS, self.rotate)
            sphereFn.setRotation(
                om.MEulerRotation(radians(r[0]), radians(r[1]), radians(r[2])),
                om.MSpace.kTransform)

        if (self.scale in self.ops):
            expectedTRS = self.updateTRS(expectedTRS, self.scale, [1, 2, 3])
            s = self.extractTRS(expectedTRS, self.scale)
            sphereFn.setScale(om.MVector(s[0], s[1], s[2]))

        spherePath = ufe.Path(mayaUtils.createUfePathSegment("|pSphere1"))
        sphereItem = ufe.Hierarchy.createItem(spherePath)

        ufe.GlobalSelection.get().append(sphereItem)

        # Create a Transform3d interface for it.
        transform3d = ufe.Transform3d.transform3d(sphereItem)

        # Set up the callables that will retrieve the translation.
        self.runTimes[self.move] = partial(sphereFn.translation,
                                           om.MSpace.kTransform)
        self.ufes[self.move] = partial(transform3dTranslation, transform3d)

        # Set up the callables that will retrieve the rotation.
        self.runTimes[self.rotate] = partial(sphereFn.rotation,
                                             om.MSpace.kTransform)
        self.ufes[self.rotate] = partial(transform3dRotation, transform3d)

        # Set up the callables that will retrieve the scale.
        self.runTimes[self.scale] = partial(sphereFn.scale)
        self.ufes[self.scale] = partial(transform3dScale, transform3d)

        self.runTestCombo(expectedTRS)

    def testComboUSD(self):
        '''Move, rotate, and scale USD object, read through the Transform3d interface.'''

        # Select Ball_35 to move, rotate, and scale it.
        ball35Path = ufe.Path([
            mayaUtils.createUfePathSegment("|transform1|proxyShape1"),
            usdUtils.createUfePathSegment("/Room_set/Props/Ball_35")
        ])
        ball35Item = ufe.Hierarchy.createItem(ball35Path)

        ufe.GlobalSelection.get().append(ball35Item)

        # Create a Transform3d interface for it.
        transform3d = ufe.Transform3d.transform3d(ball35Item)

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

        def ball35Translation():
            ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item)
            return addVec(proxyShapeXformFn.translation(om.MSpace.kTransform),
                          ball35Prim.GetAttribute('xformOp:translate').Get())

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

        def ball35Scale():
            ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item)
            if not ball35Prim.HasAttribute('xformOp:scale'):
                return proxyShapeXformFn.scale()
            else:
                return combineScales(
                    proxyShapeXformFn.scale(),
                    ball35Prim.GetAttribute('xformOp:scale').Get())

        # Set up the callables that will retrieve the translation.
        self.runTimes[self.move] = ball35Translation
        self.ufes[self.move] = partial(transform3dTranslation, transform3d)

        # Set up the callables that will retrieve the rotation.
        self.runTimes[self.rotate] = ball35Rotation
        self.ufes[self.rotate] = partial(transform3dRotation, transform3d)

        # Set up the callables that will retrieve the scale.
        self.runTimes[self.scale] = ball35Scale
        self.ufes[self.scale] = partial(transform3dScale, transform3d)

        # Save the initial position to the memento list.
        expectedTRS = None
        if (self.move in self.ops):
            v = ball35Translation()
            expectedTRS = self.updateTRS(expectedTRS, self.move,
                                         [v[0], v[1], v[2]])
        if (self.rotate in self.ops):
            r = ball35Rotation().asVector()
            expectedTRS = self.updateTRS(
                expectedTRS, self.rotate,
                [degrees(r[0]), degrees(r[1]),
                 degrees(r[2])])

        if (self.scale in self.ops):
            s = ball35Scale()
            expectedTRS = self.updateTRS(expectedTRS, self.scale,
                                         [s[0], s[1], s[2]])

        self.runTestCombo(expectedTRS)

    @unittest.skipUnless(
        mayaUtils.mayaMajorVersion() >= 2022,
        'Rotate and scale pivot compensation only available in Maya 2022 or greater.'
    )
    def testRotateScalePivotCompensation(self):
        '''Test that rotate and scale pivot compensation match Maya object.'''

        cmds.file(new=True, force=True)
        mayaSphere = cmds.polySphere()[0]
        mayaSpherePath = ufe.PathString.path('|pSphere1')
        mayaSphereItem = ufe.Hierarchy.createItem(mayaSpherePath)

        import mayaUsd_createStageWithNewLayer
        proxyShape = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()

        proxyShapePath = ufe.PathString.path(proxyShape)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Sphere'])

        usdSpherePath = ufe.PathString.path('%s,/Sphere1' % proxyShape)
        usdSphereItem = ufe.Hierarchy.createItem(usdSpherePath)
        usdSphereT3d = ufe.Transform3d.transform3d(usdSphereItem)

        # If the Transform3d interface can't handle rotate or scale pivot
        # compensation, skip this test.
        if usdSphereT3d.translateRotatePivotCmd() is None or \
           usdSphereT3d.translateScalePivotCmd() is None:
            raise unittest.SkipTest(
                "Rotate or scale pivot compensation unsupported.")

        # Select both spheres.
        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(mayaSphereItem)
        sn.append(usdSphereItem)

        # Rotate both spheres around X, and scale them.
        cmds.rotate(30, 0, 0, r=True, os=True, fo=True)
        cmds.scale(1, 1, 2, r=True)

        # Move pivots in world space.  At time of writing (20-Oct-20) UFE
        # rotate pivot and scale pivot arguments to move command doesn't accept
        # an object argument, so use the selection.
        cmds.move(0,
                  -2.104143,
                  3.139701,
                  "pSphere1.scalePivot",
                  "pSphere1.rotatePivot",
                  r=True)
        sn.remove(mayaSphereItem)
        cmds.move(0, -2.104143, 3.139701, r=True, urp=True, usp=True)

        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

        # Scale the spheres again
        sn.append(mayaSphereItem)
        cmds.scale(1, 1, 2, r=True)

        # Move the pivots again.
        cmds.move(0,
                  5.610465,
                  3.239203,
                  "pSphere1.scalePivot",
                  "pSphere1.rotatePivot",
                  r=True)
        sn.remove(mayaSphereItem)
        cmds.move(0, 5.610465, 3.239203, r=True, urp=True, usp=True)

        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

        # Move only the rotate pivot.
        cmds.move(0, 0, 3, r=True, urp=True)
        cmds.move(0, 0, 3, "pSphere1.rotatePivot", r=True)

        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

        # Move only the scale pivot.
        cmds.move(0, 0, -4, r=True, usp=True)
        cmds.move(0, 0, -4, "pSphere1.scalePivot", r=True)

        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

    @unittest.skipUnless(
        mayaUtils.mayaMajorVersion() >= 2022,
        'Rotate and scale pivot compensation only available in Maya 2022 or greater.'
    )
    def testRotateScalePivotCompensationAfterExport(self):
        '''Rotate and scale pivots must match after export.'''

        cmds.file(new=True, force=True)
        mayaSphere = cmds.polySphere()[0]

        cmds.rotate(0, 0, -45, r=True, os=True, fo=True)
        cmds.scale(4, 3, 2, r=True)
        cmds.move(-2, -3, -4, "pSphere1.rotatePivot", r=True)
        cmds.move(7, 6, 5, "pSphere1.scalePivot", r=True)

        # Export out, reference back in using proxy shape.
        usdFilePath = os.path.abspath('UsdExportMayaXformStack.usda')
        cmds.mayaUSDExport(file=usdFilePath)

        # Reference it back in.
        proxyShape = cmds.createNode('mayaUsdProxyShape')
        cmds.setAttr('mayaUsdProxyShape1.filePath', usdFilePath, type='string')

        # MAYA-101766: awkward plug access for non-interactive stage loading.
        outStageData = nameToPlug('mayaUsdProxyShape1.outStageData')
        outStageData.asMDataHandle()

        proxyShapeMayaPath = cmds.ls(proxyShape, long=True)[0]
        proxyShapePathSegment = mayaUtils.createUfePathSegment(
            proxyShapeMayaPath)

        spherePathSegment = usdUtils.createUfePathSegment('/pSphere1')
        spherePath = ufe.Path([proxyShapePathSegment, spherePathSegment])
        sphereItem = ufe.Hierarchy.createItem(spherePath)
        usdSphereT3d = ufe.Transform3d.transform3d(sphereItem)

        # If the Transform3d interface can't handle rotate or scale pivot
        # compensation, skip this test.
        if usdSphereT3d.translateRotatePivotCmd() is None or \
           usdSphereT3d.translateScalePivotCmd() is None:
            raise unittest.SkipTest(
                "Rotate or scale pivot compensation unsupported.")

        # Maya object and its exported USD object twin should have the
        # same pivots and pivot compensations.
        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

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

        # Move only the rotate pivot.
        cmds.move(-1, -2, -3, r=True, urp=True)
        cmds.move(-1, -2, -3, "pSphere1.rotatePivot", r=True)

        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

        # Move only the scale pivot.
        cmds.move(-4, -3, -2, r=True, usp=True)
        cmds.move(-4, -3, -2, "pSphere1.scalePivot", r=True)

        checkPivotsAndCompensations(self, "pSphere1", usdSphereT3d)

    @unittest.skipUnless(
        mayaUtils.mayaMajorVersion() >= 2022,
        'Fallback transform op handling only available in Maya 2022 or greater.'
    )
    def testFallbackCases(self):
        '''Fallback handler test cases.'''

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

        import mayaUsd_createStageWithNewLayer
        proxyShape = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()

        proxyShapePath = ufe.PathString.path(proxyShape)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Sphere'])

        spherePath = ufe.PathString.path('%s,/Sphere1' % proxyShape)
        sphereItem = ufe.Hierarchy.createItem(spherePath)
        sphereT3d = ufe.Transform3d.transform3d(sphereItem)

        spherePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(spherePath))
        sphereXformable = UsdGeom.Xformable(spherePrim)

        # Add transform ops that do not match either the Maya transform stack,
        # the USD common API transform stack, or a matrix stack.
        sphereXformable.AddTranslateOp()
        sphereXformable.AddTranslateOp(UsdGeom.XformOp.PrecisionFloat, "pivot")
        sphereXformable.AddRotateZOp()
        sphereXformable.AddTranslateOp(UsdGeom.XformOp.PrecisionFloat, "pivot",
                                       True)

        self.assertEqual(
            sphereXformable.GetXformOpOrderAttr().Get(),
            Vt.TokenArray(
                ("xformOp:translate", "xformOp:translate:pivot",
                 "xformOp:rotateZ", "!invert!xformOp:translate:pivot")))

        self.assertFalse(UsdGeom.XformCommonAPI(sphereXformable))
        self.assertFalse(mayaUsd.lib.XformStack.MayaStack().MatchingSubstack(
            sphereXformable.GetOrderedXformOps()))

        # Select sphere.
        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(sphereItem)

        # Rotate sphere around X.
        cmds.rotate(30, 0, 0, r=True, os=True, fo=True)

        # Fallback interface will have added a RotXYZ transform op.
        self.assertEqual(
            sphereXformable.GetXformOpOrderAttr().Get(),
            Vt.TokenArray(
                ("xformOp:translate", "xformOp:translate:pivot",
                 "xformOp:rotateZ", "!invert!xformOp:translate:pivot",
                 "xformOp:rotateXYZ:maya_fallback")))

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 123,
        'Requires Maya fixes only available in Maya Preview Release 123 or later.'
    )
    def testBrokenFallback(self):
        '''Maya fallback transform stack must be final on prim transform op stack.'''
        # Create a prim and add transform ops to it that don't match the Maya
        # transform stack.  Then, transform it with Maya: this will trigger the
        # creation of a Maya fallback transform stack.  Finally, append another
        # transform op to the prim transform stack.  Because there is now a
        # transform op beyond the Maya fallback transform stack, the Maya
        # fallback has been "corrupted", and any further transformation of the
        # prim must be a no-op.

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

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

        capsulePath = ufe.PathString.path('%s,/Capsule1' % proxyShape)
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)
        capsulePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(capsulePath))
        capsuleXformable = UsdGeom.Xformable(capsulePrim)
        capsuleXformable.AddRotateXOp()
        capsuleXformable.AddRotateYOp()

        self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                         Vt.TokenArray(("xformOp:rotateX", "xformOp:rotateY")))

        # Select capsule.
        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(capsuleItem)

        # Rotate sphere around X.
        cmds.rotate(30, 0, 0, r=True, os=True, fo=True)

        # Fallback interface will have added a RotXYZ transform op.
        self.assertEqual(
            capsuleXformable.GetXformOpOrderAttr().Get(),
            Vt.TokenArray(("xformOp:rotateX", "xformOp:rotateY",
                           "xformOp:rotateXYZ:maya_fallback")))

        capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
        self.assertIsNotNone(capsuleT3d)

        # Add another transform op to break the Maya fallback stack.
        capsuleXformable.AddRotateZOp()

        self.assertEqual(
            capsuleXformable.GetXformOpOrderAttr().Get(),
            Vt.TokenArray(
                ("xformOp:rotateX", "xformOp:rotateY",
                 "xformOp:rotateXYZ:maya_fallback", "xformOp:rotateZ")))

        # Do any transform editing with Maya.
        cmds.rotate(0, 0, 30, r=True, os=True, fo=True)

        capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
        self.assertIsNone(capsuleT3d)

    @unittest.skipIf(
        mayaUtils.previewReleaseVersion() < 123,
        'Fallback transform op handling only available in Maya Preview Release 123 or later.'
    )
    def testFallback(self):
        '''Transformable not handled by standard Transform3d handlers must be
    handled by fallback handler.'''

        mayaUtils.openTestScene("xformOpFallback", "fallbackTest.ma")

        # We have three objects in the scene, one Maya, one USD with a Maya
        # transform stack, and one USD which does not match any Transform3d
        # handler.  This last object is the one to which fallback transform ops
        # will be appended.
        mayaObj = '|null1|pSphere1'
        mayaSpherePath = ufe.PathString.path(mayaObj)
        usdSpherePath = ufe.PathString.path(
            '|fallbackTest|fallbackTestShape,/parent/sphere1')
        usdFallbackSpherePath = ufe.PathString.path(
            '|fallbackTest|fallbackTestShape,/sphere1')

        mayaSphereItem = ufe.Hierarchy.createItem(mayaSpherePath)
        usdSphereItem = ufe.Hierarchy.createItem(usdSpherePath)
        usdFallbackSphereItem = ufe.Hierarchy.createItem(usdFallbackSpherePath)
        # For scene items with fallback transform ops, the transform3d()
        # interface considers the complete object (i.e. all transform ops in
        # the stack), which is undesirable when setting and getting fallback
        # pivot transform ops.  To consider only the fallback transform ops,
        # use the editTransform3d() interface.  For scene items with only a
        # Maya transform stack, editTransform3d() and transform3d() are
        # equivalent, so arbitrarily choose editTransform3d().
        usdSphere3d = ufe.Transform3d.editTransform3d(
            usdSphereItem, ufe.EditTransform3dHint())
        usdFallbackSphere3d = ufe.Transform3d.editTransform3d(
            usdFallbackSphereItem, ufe.EditTransform3dHint())

        sn = ufe.GlobalSelection.get()
        sn.clear()
        sn.append(mayaSphereItem)
        sn.append(usdSphereItem)
        sn.append(usdFallbackSphereItem)

        # All objects should have the same world transform.  We use the Maya
        # world transform as the benchmark.
        checkWorldSpaceXform(self, [mayaObj, usdSphere3d, usdFallbackSphere3d])

        # Count the number of transform ops in the Maya transform stack sphere
        # and the fallback transform stack sphere.
        spherePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(usdSpherePath))
        fallbackSpherePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(usdFallbackSpherePath))
        sphereXformable = UsdGeom.Xformable(spherePrim)
        fallbackSphereXformable = UsdGeom.Xformable(fallbackSpherePrim)
        sphereOps = sphereXformable.GetOrderedXformOps()
        fallbackSphereOps = fallbackSphereXformable.GetOrderedXformOps()

        # Both prims have TRS transform ops.
        self.assertEqual(len(sphereOps), 3)
        self.assertEqual(len(fallbackSphereOps), 3)

        # First, translate all objects.
        cmds.move(0, 0, 5, r=True, os=True, wd=True)

        checkWorldSpaceXform(self, [mayaObj, usdSphere3d, usdFallbackSphere3d])

        # The sphere with the Maya transform stack has no additional transform
        # op; the sphere with the fallback stack will have an additional
        # translate op.
        sphereOps = sphereXformable.GetOrderedXformOps()
        fallbackSphereOps = fallbackSphereXformable.GetOrderedXformOps()

        self.assertEqual(len(sphereOps), 3)
        self.assertEqual(len(fallbackSphereOps), 4)

        # Rotate
        cmds.rotate(40, 0, 0, r=True, os=True, fo=True)

        checkWorldSpaceXform(self, [mayaObj, usdSphere3d, usdFallbackSphere3d])

        # The sphere with the Maya transform stack has no additional transform
        # op; the sphere with the fallback stack will have an additional
        # rotate op.
        sphereOps = sphereXformable.GetOrderedXformOps()
        fallbackSphereOps = fallbackSphereXformable.GetOrderedXformOps()

        self.assertEqual(len(sphereOps), 3)
        self.assertEqual(len(fallbackSphereOps), 5)

        # Scale
        cmds.scale(1, 1, 2.0, r=True)

        checkWorldSpaceXform(self, [mayaObj, usdSphere3d, usdFallbackSphere3d])

        # The sphere with the Maya transform stack has no additional transform
        # op; the sphere with the fallback stack will have an additional
        # scale op.
        sphereOps = sphereXformable.GetOrderedXformOps()
        fallbackSphereOps = fallbackSphereXformable.GetOrderedXformOps()

        self.assertEqual(len(sphereOps), 3)
        self.assertEqual(len(fallbackSphereOps), 6)

        # Command to change the pivots on Maya items and UFE items is
        # different, so remove Maya item from selection.
        sn.remove(mayaSphereItem)

        mayaPivots = [
            mayaObj + "." + attrName
            for attrName in ["scalePivot", "rotatePivot"]
        ]
        cmds.move(0, -2.5, 2.5, *mayaPivots, r=True)
        cmds.move(0, -2.5, 2.5, r=True, urp=True, usp=True)

        checkPivotsAndCompensations(self, mayaObj, usdSphere3d)
        checkPivotsAndCompensations(self, mayaObj, usdFallbackSphere3d)

        # Both spheres have 6 additional transform ops: rotate pivot and its
        # inverse, scale pivot and its inverse, rotate pivot translate, and
        # scale pivot translate.
        sphereOps = sphereXformable.GetOrderedXformOps()
        fallbackSphereOps = fallbackSphereXformable.GetOrderedXformOps()

        self.assertEqual(len(sphereOps), 9)
        self.assertEqual(len(fallbackSphereOps), 12)

        # Perform an additional pivot move, to ensure that the existing pivot
        # values are properly considered.
        cmds.move(0, -1, 1, *mayaPivots, r=True)
        cmds.move(0, -1, 1, r=True, urp=True, usp=True)

        checkPivotsAndCompensations(self, mayaObj, usdSphere3d)
        checkPivotsAndCompensations(self, mayaObj, usdFallbackSphere3d)

    @unittest.skipIf(
        int(cmds.about(apiVersion=True)) <= 20220000,
        'Center pivot command is only available in Maya 2022 or later.')
    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])

    @unittest.skipUnless(
        ufeUtils.ufeFeatureSetVersion() >= 2,
        'testPrimPropertyPathNotifs only available in UFE v2 or greater.')
    def testPrimPropertyPathNotifs(self):
        import mayaUsd_createStageWithNewLayer
        proxyShape = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        proxyShapePath = ufe.PathString.path(proxyShape)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])

        # Select the capsule
        capPath = ufe.PathString.path('%s,/Capsule1' % proxyShape)
        capItem = ufe.Hierarchy.createItem(capPath)
        ufe.GlobalSelection.get().clear()
        ufe.GlobalSelection.get().append(capItem)

        # No notifications yet.
        obs = TestObserver()
        ufe.Attributes.addObserver(capItem, obs)
        ufe.Transform3d.addObserver(capItem, obs)
        self.assertEqual(obs.notifications, 0)

        # Move the capsule
        cmds.move(0, 10, 10)

        # Verify that we got both ValueChanged and Transform3d notifs.
        # Note: we should get notifs on both the "xformOp:translate" and
        #       "xformOpOrder" attributes. We don't care how many, just that
        #       we are getting both of these notifs kinds on both the move
        #       and undo.
        self.assertTrue(obs.nbValueChanged > 0)
        self.assertTrue(obs.nbTransform3d > 0)

        # Reset observer and then undo and again verify notifs.
        obs.reset()
        self.assertEqual(obs.notifications, 0)
        cmds.undo()
        self.assertTrue(obs.nbValueChanged > 0)
        self.assertTrue(obs.nbTransform3d > 0)

        # Reset and test same thing with Rotate.
        obs.reset()
        cmds.rotate(10, 0, 0)
        self.assertTrue(obs.nbValueChanged > 0)
        self.assertTrue(obs.nbTransform3d > 0)
        obs.reset()
        self.assertEqual(obs.notifications, 0)
        cmds.undo()
        self.assertTrue(obs.nbValueChanged > 0)
        self.assertTrue(obs.nbTransform3d > 0)

        # Reset and test same thing with Scale.
        obs.reset()
        cmds.scale(2, 2, 2)
        self.assertTrue(obs.nbValueChanged > 0)
        self.assertTrue(obs.nbTransform3d > 0)
        obs.reset()
        self.assertEqual(obs.notifications, 0)
        cmds.undo()
        self.assertTrue(obs.nbValueChanged > 0)
        self.assertTrue(obs.nbTransform3d > 0)
예제 #4
0
class CameraTestCase(unittest.TestCase):
    '''Verify the Camera UFE translate interface, for multiple runtimes.
    
    UFE Feature : Camera
    Maya Feature : camera
    Action : set & read camera parameters
    Applied On Selection : No
    Undo/Redo Test : No
    Expect Results To Test :
        - Setting a value through Ufe correctly updates USD
        - Reading a value through Ufe gets the correct value
    Edge Cases :
        - None.

    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        standalone.uninitialize()

    def setUp(self):
        ''' Called initially to set up the maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # value from UsdGeomLinearUnits
        self.inchesToCm = 2.54
        self.mmToCm = 0.1

    def _StartTest(self, testName):
        cmds.file(force=True, new=True)
        mayaUtils.loadPlugin("mayaUsdPlugin")
        self._testName = testName
        testFile = testUtils.getTestScene("camera", self._testName + ".usda")
        mayaUtils.createProxyFromFile(testFile)
        globalSelection = ufe.GlobalSelection.get()
        globalSelection.clear()

    def _TestCameraAttributes(self, ufeCamera, usdCamera):
        # Trust that the USD API works correctly, validate that UFE gives us
        # the same answers
        self._TestHorizontalAperture(ufeCamera, usdCamera)
        self._TestVerticalAperture(ufeCamera, usdCamera)
        self._TestHorizontalApertureOffset(ufeCamera, usdCamera)
        self._TestVerticalApertureOffset(ufeCamera, usdCamera)
        self._TestFStop(ufeCamera, usdCamera)
        self._TestFocalLength(ufeCamera, usdCamera)
        self._TestFocusDistance(ufeCamera, usdCamera)
        self._TestClippingRange(ufeCamera, usdCamera)
        self._TestProjection(ufeCamera, usdCamera)

    def _TestClippingRange(self, ufeCamera, usdCamera):
        clippingAttr = usdCamera.GetAttribute('clippingRange')
        clippingRange = clippingAttr.Get()

        # read the nearClipPlane and check the value is what we expect.
        self.assertAlmostEqual(ufeCamera.nearClipPlane(), clippingRange[0])
        self.assertAlmostEqual(ufeCamera.farClipPlane(), clippingRange[1])

        # setting camera values through Ufe is not yet implemented in MayaUSD
        # ufeCamera.nearClipPlane(10)
        # ufeCamera.farClipPlane(20)
        # clippingRange = clippingAttr.Get()
        # self.assertAlmostEqual(10, clippingRange[0])
        # self.assertAlmostEqual(20, clippingRange[1])

        # set the clipping planes using USD
        clippingAttr.Set(Gf.Vec2f(1, 50))

        # read the nearClipPlane and check the value is what we just set.
        self.assertAlmostEqual(ufeCamera.nearClipPlane(), 1)
        self.assertAlmostEqual(ufeCamera.farClipPlane(), 50)

    def _TestHorizontalAperture(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('horizontalAperture')
        horizontalAperture = usdAttr.Get()

        # the USD schema specifics that horizontal aperture is authored in mm
        # the ufeCamera gives us inches
        self.assertAlmostEqual(
            self.mmToCm * horizontalAperture,
            self.inchesToCm * ufeCamera.horizontalAperture())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(
            self.mmToCm * 0.5,
            self.inchesToCm * ufeCamera.horizontalAperture())

    def _TestVerticalAperture(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('verticalAperture')
        verticalAperture = usdAttr.Get()

        self.assertAlmostEqual(self.mmToCm * verticalAperture,
                               self.inchesToCm * ufeCamera.verticalAperture())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(self.mmToCm * 0.5,
                               self.inchesToCm * ufeCamera.verticalAperture())

    def _TestHorizontalApertureOffset(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('horizontalApertureOffset')
        horizontalApertureOffset = usdAttr.Get()

        self.assertAlmostEqual(
            self.mmToCm * horizontalApertureOffset,
            self.inchesToCm * ufeCamera.horizontalApertureOffset())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(
            self.mmToCm * 0.5,
            self.inchesToCm * ufeCamera.horizontalApertureOffset())

    def _TestVerticalApertureOffset(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('verticalApertureOffset')
        verticalApertureOffset = usdAttr.Get()

        self.assertAlmostEqual(
            self.mmToCm * verticalApertureOffset,
            self.inchesToCm * ufeCamera.verticalApertureOffset())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(
            self.mmToCm * 0.5,
            self.inchesToCm * ufeCamera.verticalApertureOffset())

    def _TestFStop(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('fStop')
        fStop = usdAttr.Get()

        # precision error here from converting units so use a less precise comparison, 6 digits instead of 7
        self.assertAlmostEqual(fStop, self.mmToCm * ufeCamera.fStop(), 6)

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(0.5, self.mmToCm * ufeCamera.fStop(), 6)

    def _TestFocalLength(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('focalLength')
        focalLength = usdAttr.Get()

        self.assertAlmostEqual(self.mmToCm * focalLength,
                               self.mmToCm * ufeCamera.focalLength())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(self.mmToCm * 0.5,
                               self.mmToCm * ufeCamera.focalLength())

    def _TestFocusDistance(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('focusDistance')
        focusDistance = usdAttr.Get()

        self.assertAlmostEqual(self.mmToCm * focusDistance,
                               self.mmToCm * ufeCamera.focusDistance())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set(0.5)
        self.assertAlmostEqual(self.mmToCm * 0.5,
                               self.mmToCm * ufeCamera.focusDistance())

    def _TestProjection(self, ufeCamera, usdCamera):
        usdAttr = usdCamera.GetAttribute('projection')
        projection = usdAttr.Get()

        self.assertEqual(ufe.Camera.Perspective, ufeCamera.projection())

        # setting camera values through Ufe is not yet implemented in MayaUSD

        usdAttr.Set("orthographic")
        self.assertAlmostEqual(ufe.Camera.Orthographic, ufeCamera.projection())

    @unittest.skipIf(
        mayaUtils.previewReleaseVersion() <= 124,
        'Missing Python API for Ufe::Camera before Maya Preview Release 125.')
    def testUsdCamera(self):
        self._StartTest('TranslateRotate_vs_xform')
        mayaPathSegment = mayaUtils.createUfePathSegment('|stage|stageShape')
        cam2UsdPathSegment = usdUtils.createUfePathSegment(
            '/cameras/cam2/camera2')
        cam2Path = ufe.Path([mayaPathSegment, cam2UsdPathSegment])
        cam2Item = ufe.Hierarchy.createItem(cam2Path)

        cam2Camera = ufe.Camera.camera(cam2Item)
        cameraPrim = usdUtils.getPrimFromSceneItem(cam2Item)
        self._TestCameraAttributes(cam2Camera, cameraPrim)
class RotatePivotTestCase(unittest.TestCase):
    '''Verify the Transform3d UFE rotate pivot interface.

    UFE Feature : Transform3d
    Maya Feature : rotate pivot
    Action : Set the rotate pivot.
    Applied On Selection : 
        - No selection - Given node as param
        - Single Selection : Not tested.
        - Multiple Selection [Mixed, Non-Maya] : Not tested.

    Undo/Redo Test : UFE undoable command only.
    Expect Results To Test :
        - Maya Dag object world space position.
        - USD object world space position.
    Edge Cases :
        - None.
    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        standalone.uninitialize()

    def setUp(self):
        ''' Called initially to set up the maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Open twoSpheres.ma scene in testSamples
        mayaUtils.openTwoSpheresScene()

    def checkPos(self, m, p):
        self.assertAlmostEqual(m[ndx(3, 0)], p[0])
        self.assertAlmostEqual(m[ndx(3, 1)], p[1])
        self.assertAlmostEqual(m[ndx(3, 2)], p[2])

    def testRotatePivot(self):
        # mayaSphere is at (10, 0, 10) in local space, and since it has no
        # parent, in world space as well.
        spherePath = om.MSelectionList().add('mayaSphere').getDagPath(0)
        sphereFn = om.MFnTransform(spherePath)
        rotZ = radians(45)
        rot = om.MEulerRotation(0, 0, rotZ)
        # Pivot around x=0.
        pivot = om.MPoint(-10, 0, 0)
        sphereFn.setRotatePivot(pivot, om.MSpace.kTransform, False)
        sphereFn.setRotation(rot, om.MSpace.kTransform)
        # MFnTransform and MTransformationMatrix always return the individual
        # components of the matrix, including translation, which is unaffected
        # by pivot changes, as expected.  The fully-multiplied result is in the
        # computed MMatrix.
        xyWorldValue = sin(rotZ) * 10
        sphereMatrix = sphereFn.transformation().asMatrix()

        self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 10])

        # Do the same with the USD object, through UFE.
        # USD sphere is at (10, 0, 0) in local space, and since its parents
        # have an identity transform, in world space as well.
        usdSpherePath = ufe.PathString.path(
            '|usdSphereParent|usdSphereParentShape,/sphereXform/sphere')
        usdSphereItem = ufe.Hierarchy.createItem(usdSpherePath)
        t3d = ufe.Transform3d.transform3d(usdSphereItem)

        t3d.rotatePivot(pivot[0], pivot[1], pivot[2])
        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), pivot)

        t3d.rotate(degrees(rot[0]), degrees(rot[1]), degrees(rot[2]))
        sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
        self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 0])

        t3d.rotatePivot(0, 0, 0)
        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(0, 0, 0))

        sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
        self.checkPos(sphereMatrix, [10, 0, 0])

        # Use a UFE undoable command to set the pivot.
        rotatePivotCmd = t3d.rotatePivotCmd()
        rotatePivotCmd.set(pivot[0], pivot[1], pivot[2])

        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), pivot)

        sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)

        self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 0])

        rotatePivotCmd.undo()

        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(0, 0, 0))

        sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
        self.checkPos(sphereMatrix, [10, 0, 0])

        # redo() cannot be tested, as it currently is intentionally not
        # implemented, because the Maya move command handles undo by directly
        # calling the translate() method.  This is fragile,
        # implementation-specific, and should be changed.  PPT, 3-Sep-2020.

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 119,
        'Requires Maya fixes only available in Maya Preview Release 119 or later.'
    )
    def testRotatePivotCmd(self):
        rotZ = radians(45)
        rot = om.MEulerRotation(0, 0, rotZ)
        # Pivot around x=0.
        pivot = om.MPoint(-10, 0, 0)
        xyWorldValue = sin(rotZ) * 10

        # USD sphere is at (10, 0, 0) in local space, and since its parents
        # have an identity transform, in world space as well.
        spherePath = ufe.PathString.path(
            '|usdSphereParent|usdSphereParentShape,/sphereXform/sphere')
        sphereItem = ufe.Hierarchy.createItem(spherePath)
        ufe.GlobalSelection.get().append(sphereItem)

        # Create a Transform3d interface to read from USD.
        t3d = ufe.Transform3d.transform3d(sphereItem)

        # Start with a non-zero initial rotate pivot.  This is required to test
        # MAYA-105345, otherwise a zero initial rotate pivot produces the
        # correct result through an unintended code path.
        t3d.rotatePivot(2, 0, 0)
        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(2, 0, 0))

        cmds.move(-12, 0, 0, relative=True, ufeRotatePivot=True)
        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), pivot)

        cmds.undo()

        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(2, 0, 0))

        cmds.redo()

        usdPivot = t3d.rotatePivot()
        self.assertEqual(v3dToMPoint(usdPivot), pivot)

        cmds.rotate(degrees(rot[0]), degrees(rot[1]), degrees(rot[2]))
        sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
        self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 0])
예제 #6
0
    def testRotatePivotCmd(self):
        rotZ = radians(45)
        rot = om.MEulerRotation(0, 0, rotZ)
        # Pivot around x=0.
        pivot = om.MPoint(-10, 0, 0)
        pivotTranslate = om.MPoint(0, 0, 0)
        xyWorldValue = sin(rotZ) * 10
        rotatedPosition = [xyWorldValue, xyWorldValue, 0]

        # USD sphere is at (10, 0, 0) in local space, and since its parents
        # have an identity transform, in world space as well.
        spherePath = ufe.PathString.path(
            '|usdSphereParent|usdSphereParentShape,/sphereXform')
        sphereItem = ufe.Hierarchy.createItem(spherePath)
        ufe.GlobalSelection.get().append(sphereItem)

        # Create a Transform3d interface to read from USD.
        t3d = ufe.Transform3d.transform3d(sphereItem)

        # Start with a non-zero initial rotate pivot.  This is required to test
        # MAYA-105345, otherwise a zero initial rotate pivot produces the
        # correct result through an unintended code path.
        t3d.rotatePivot(2, 0, 0)
        usdPivot = t3d.rotatePivot()
        assertMPointAlmostEqual(self, v3dToMPoint(usdPivot),
                                om.MPoint(2, 0, 0))
        t3d.rotatePivot(0, 0, 0)

        # Start with a non-zero initial rotation. This is required to test
        # MAYA-112175, otherwise a zero initial rotation means rotate pivot
        # translation will be empty and we get the correct result by accident.
        if (mayaUtils.previewReleaseVersion() >= 127):
            cmds.rotate(0, 0, 90)
            print(type(pivot))
            pivot = om.MPoint(0, 10, 0)
            print(type(pivot))
            pivotTranslate = om.MPoint(-10, -10, 0)
            rotatedPosition = [xyWorldValue, -xyWorldValue, 0]

        cmds.move(-10, 0, 0, relative=True, ufeRotatePivot=True)
        usdPivot = t3d.rotatePivot()
        assertMPointAlmostEqual(self, v3dToMPoint(usdPivot), pivot)
        usdRotatePivotTranslation = t3d.rotatePivotTranslation()
        assertMPointAlmostEqual(self, v3dToMPoint(usdRotatePivotTranslation),
                                pivotTranslate)

        cmds.undo()

        usdPivot = t3d.rotatePivot()
        assertMPointAlmostEqual(self, v3dToMPoint(usdPivot),
                                om.MPoint(0, 0, 0))
        usdRotatePivotTranslation = t3d.rotatePivotTranslation()
        assertMPointAlmostEqual(self, v3dToMPoint(usdRotatePivotTranslation),
                                om.MPoint(0, 0, 0))

        cmds.redo()

        usdPivot = t3d.rotatePivot()
        assertMPointAlmostEqual(self, v3dToMPoint(usdPivot), pivot)

        cmds.rotate(degrees(rot[0]), degrees(rot[1]), degrees(rot[2]))
        sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
        self.checkPos(sphereMatrix, rotatedPosition)

        if (mayaUtils.previewReleaseVersion() >= 127):
            cmds.undo()
            cmds.move(10, 10, 0, absolute=True)
            sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
            self.checkPos(sphereMatrix, [10, 10, 0])
            cmds.move(10, 10, 0, absolute=True, rotatePivotRelative=True)
            sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix)
            self.checkPos(sphereMatrix, [20, 10, 0])
예제 #7
0
# This template file is no longer required for Maya PR 121.
# It will eventually be removed.
import mayaUtils
if mayaUtils.previewReleaseVersion() < 121:
    import ufe_ae.usd.nodes.usdschemabase.ae_template as UsdSchemaBaseAETemplate

    # Base template class that can be used by other templates.
    # Nov 2020: New schema base template. Eventually we will
    #           remove the specific node and this base template.
    class AEBaseTemplate(UsdSchemaBaseAETemplate.AETemplate):
        def __init__(self, ufeSceneItem):
            super(AEBaseTemplate, self).__init__(ufeSceneItem)

        def buildUI(self, ufeSceneItem):
            super(AEBaseTemplate, self).buildUI(ufeSceneItem)
예제 #8
0
class ParentCmdTestCase(unittest.TestCase):
    '''Verify the Maya parent command on a USD scene.'''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        cmds.file(new=True, force=True)

        standalone.uninitialize()

    def setUp(self):
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Load a file that has the same scene in both the Maya Dag
        # hierarchy and the USD hierarchy.
        mayaUtils.openTestScene("parentCmd", "simpleSceneMayaPlusUSD_TRS.ma")

        # Clear selection to start off
        cmds.select(clear=True)

        # Save current 'MAYA_USD_MATRIX_XFORM_OP_NAME' env var, if present.
        self.mayaUsdMatrixXformOpName = os.environ.get(
            'MAYA_USD_MATRIX_XFORM_OP_NAME')

    def tearDown(self):
        # If there was no 'MAYA_USD_MATRIX_XFORM_OP_NAME' environment variable,
        # make sure there is none at the end of the test.
        if self.mayaUsdMatrixXformOpName is None:
            if 'MAYA_USD_MATRIX_XFORM_OP_NAME' in os.environ:
                del os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME']
        else:
            # Restore previous value.
            os.environ['MAYA_USD_MATRIX_XFORM_OP_NAME'] = \
                self.mayaUsdMatrixXformOpName

    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)

    @unittest.skipIf(mayaUtils.previewReleaseVersion() == 122,
                     'Test broken in Maya Preview Release 122.')
    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)

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 123,
        'Requires Maya fixes only available in Maya Preview Release 123 or later.'
    )
    def testParentAbsoluteSingleMatrixOp(self):
        """Test parent -absolute on prim with a single matrix op."""

        # Our test strategy is the following: we use the existing scene's cube
        # prim as reference, and set its local matrix onto a new prim that has
        # a single matrix op.  The cube prim is used as a reference.
        #
        # We then parent -absolute the new prim as well as the cube, and assert
        # that neither the new prim or the cube have moved in world space.

        # Create scene items for the cube, the cylinder, and the proxy shape.
        proxyShapePathStr = '|mayaUsdProxy1|mayaUsdProxyShape1'
        proxyShapePath = ufe.PathString.path(proxyShapePathStr)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        shapeSegment = mayaUtils.createUfePathSegment(proxyShapePathStr)
        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())

        # Create a capsule prim directly under the proxy shape
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])

        capsulePath = ufe.PathString.path(proxyShapePathStr + ',/Capsule1')

        capsulePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(capsulePath))
        capsuleXformable = UsdGeom.Xformable(capsulePrim)

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

        # Add a single matrix transform op to the capsule.
        capsulePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(capsulePath))
        capsuleXformable = UsdGeom.Xformable(capsulePrim)
        capsuleXformable.AddTransformOp()

        self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                         Vt.TokenArray(["xformOp:transform"]))

        # Delay creating the Transform3d interface until after we've added our
        # single matrix transform op, so that we get a UsdTransform3dMatrixOp.
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)
        capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)

        # The cube is a direct child of the proxy shape, whose transform is the
        # identity, so the cube's world and local space transforms are identical
        cubeT3d = ufe.Transform3d.transform3d(cubeItem)
        cubeLocal = cubeT3d.matrix()
        cubeWorld = cubeT3d.inclusiveMatrix()
        cubeWorldListPre = matrixToList(cubeWorld)

        # Set the capsule's transform to be the same as the cube's.
        capsuleT3d.setMatrix(cubeLocal)

        self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                         Vt.TokenArray(["xformOp:transform"]))

        capsuleLocal = capsuleT3d.matrix()
        capsuleWorld = capsuleT3d.inclusiveMatrix()
        capsuleWorldListPre = matrixToList(capsuleWorld)

        assertVectorAlmostEqual(self, matrixToList(cubeLocal),
                                matrixToList(capsuleLocal))
        assertVectorAlmostEqual(self, matrixToList(cubeWorld),
                                matrixToList(capsuleWorld))

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

        # Confirm that the cube and capsule are now children of the cylinder.
        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 3)
        self.assertIn("cubeXform", childrenNames(cylChildren))
        self.assertIn("Capsule1", childrenNames(cylChildren))

        # Undo: cylinder no longer has children.
        cmds.undo()

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

        # Redo: confirm children are back.
        cmds.redo()

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

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

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

        # Undo everything.
        cmds.undo()

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

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 123,
        'Requires Maya fixes only available in Maya Preview Release 123 or later.'
    )
    def testParentAbsoluteFallback(self):
        """Test parent -absolute on prim with a fallback Maya transform stack."""
        # 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 and capsule to set up their initial
        # transform ops.
        xformPath = ufe.Path(
            [proxyShapePathSegment,
             usdUtils.createUfePathSegment('/Xform1')])
        xformItem = ufe.Hierarchy.createItem(xformPath)
        capsulePath = ufe.Path([
            proxyShapePathSegment,
            usdUtils.createUfePathSegment('/Capsule1')
        ])
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)

        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()
        sn.append(capsuleItem)

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

        # Add an extra rotate transform op.  In so doing, the capsule prim no
        # longer matches the Maya transform stack, so our parent -absolute
        # operation will be forced to append Maya fallback transform stack ops
        # to the capsule.
        capsuleXformable.AddRotateXOp()
        self.assertEqual(
            capsuleXformable.GetXformOpOrderAttr().Get(),
            Vt.TokenArray(
                ("xformOp:translate", "xformOp:rotateXYZ", "xformOp:rotateX")))

        capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)
        capsuleWorld = capsuleT3d.inclusiveMatrix()
        capsuleWorldPre = matrixToList(capsuleWorld)

        # 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, 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")))

        checkParentDone()

        # Undo: the xform no longer has a child, the capsule is still where it
        # has always been, and the fallback transform ops are gone.
        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(
                ("xformOp:translate", "xformOp:rotateXYZ", "xformOp:rotateX")))

        # Redo: capsule still hasn't moved, Maya fallback ops are back.
        cmds.redo()

        checkParentDone()

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 123,
        'Requires Maya fixes only available in Maya Preview Release 123 or later.'
    )
    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()

    @unittest.skipIf(mayaUtils.previewReleaseVersion() == 122,
                     'Test broken in Maya Preview Release 122.')
    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))

    def testIllegalChild(self):
        '''Parenting an object to a descendant must report an error.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            with self.assertRaises(RuntimeError):
                cmds.parent(
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1",
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1")

    def testAlreadyChild(self):
        '''Parenting an object to its current parent is a no-op.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            shapeSegment = mayaUtils.createUfePathSegment(
                "|mayaUsdProxy1|mayaUsdProxyShape1")
            spherePath = ufe.Path([
                shapeSegment,
                usdUtils.createUfePathSegment("/pCylinder1/pCube1/pSphere1")
            ])
            sphereItem = ufe.Hierarchy.createItem(spherePath)
            cylinderShapePath = ufe.Path([
                shapeSegment,
                usdUtils.createUfePathSegment("/pCylinder1/pCylinderShape1")
            ])
            cylinderShapeItem = ufe.Hierarchy.createItem(cylinderShapePath)
            parentPath = ufe.Path(
                [shapeSegment,
                 usdUtils.createUfePathSegment("/pCylinder1")])
            parentItem = ufe.Hierarchy.createItem(parentPath)

            parent = ufe.Hierarchy.hierarchy(parentItem)
            childrenPre = parent.children()

            # 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 cylinder
            self.assertNotIn("pSphere1", childrenNames(childrenPre))

            # The cylinder shape is a child of the cylinder
            self.assertIn("pCylinderShape1", childrenNames(childrenPre))

            # Parent the sphere and the cylinder shape to the cylinder.  This
            # is a no-op for the cylinder shape, as it's already a child of the
            # cylinder, and the sphere is parented to the cylinder.
            cmds.parent(ufe.PathString.string(spherePath),
                        ufe.PathString.string(cylinderShapePath),
                        ufe.PathString.string(parentPath))

            children = parent.children()
            self.assertEqual(len(childrenPre) + 1, len(children))
            self.assertIn("pSphere1", childrenNames(children))
            self.assertIn("pCylinderShape1", childrenNames(children))

            # Undo / redo
            cmds.undo()

            children = parent.children()
            self.assertEqual(len(childrenPre), len(children))
            self.assertNotIn("pSphere1", childrenNames(children))
            self.assertIn("pCylinderShape1", childrenNames(children))

            cmds.redo()

            children = parent.children()
            self.assertEqual(len(childrenPre) + 1, len(children))
            self.assertIn("pSphere1", childrenNames(children))
            self.assertIn("pCylinderShape1", childrenNames(children))

    def testUnparentUSD(self):
        '''Unparent USD node.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            # Unparent a USD node
            cubePathStr = '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1'
            cubePath = ufe.PathString.path(cubePathStr)
            cylinderItem = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1'))
            proxyShapeItem = ufe.Hierarchy.createItem(
                ufe.PathString.path('|mayaUsdProxy1|mayaUsdProxyShape1'))
            proxyShape = ufe.Hierarchy.hierarchy(proxyShapeItem)
            cylinder = ufe.Hierarchy.hierarchy(cylinderItem)

            def checkUnparent(done):
                proxyShapeChildren = proxyShape.children()
                cylinderChildren = cylinder.children()
                self.assertEqual('pCube1' in childrenNames(proxyShapeChildren),
                                 done)
                self.assertEqual('pCube1' in childrenNames(cylinderChildren),
                                 not done)

            checkUnparent(done=False)

            cmds.parent(cubePathStr, world=True)
            checkUnparent(done=True)

            cmds.undo()
            checkUnparent(done=False)

            cmds.redo()
            checkUnparent(done=True)

    def testUnparentMultiStage(self):
        '''Unparent USD nodes in more than one stage.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            # An early version of this test imported the same file into the
            # opened file.  Layers are then shared between the stages, because
            # they come from the same USD file, causing changes done below one
            # proxy shape to be seen in the other.  Import from another file.
            filePath = testUtils.getTestScene("parentCmd",
                                              "simpleSceneUSD_TRS.ma")
            cmds.file(filePath, i=True)

            # Unparent a USD node in each stage.  Unparenting Lambert node is
            # nonsensical, but demonstrates the functionality.
            cubePathStr1 = '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1'
            lambertPathStr2 = '|simpleSceneUSD_TRS_mayaUsdProxy1|simpleSceneUSD_TRS_mayaUsdProxyShape1,/initialShadingGroup/initialShadingGroup_lambert'

            cylinderItem1 = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1'))
            shadingGroupItem2 = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|simpleSceneUSD_TRS_mayaUsdProxy1|simpleSceneUSD_TRS_mayaUsdProxyShape1,/initialShadingGroup'
                ))
            proxyShapeItem1 = ufe.Hierarchy.createItem(
                ufe.PathString.path('|mayaUsdProxy1|mayaUsdProxyShape1'))
            proxyShapeItem2 = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|simpleSceneUSD_TRS_mayaUsdProxy1|simpleSceneUSD_TRS_mayaUsdProxyShape1'
                ))
            cylinder1 = ufe.Hierarchy.hierarchy(cylinderItem1)
            shadingGroup2 = ufe.Hierarchy.hierarchy(shadingGroupItem2)
            proxyShape1 = ufe.Hierarchy.hierarchy(proxyShapeItem1)
            proxyShape2 = ufe.Hierarchy.hierarchy(proxyShapeItem2)

            def checkUnparent(done):
                proxyShape1Children = proxyShape1.children()
                proxyShape2Children = proxyShape2.children()
                cylinder1Children = cylinder1.children()
                shadingGroup2Children = shadingGroup2.children()
                self.assertEqual(
                    'pCube1' in childrenNames(proxyShape1Children), done)
                self.assertEqual('pCube1' in childrenNames(cylinder1Children),
                                 not done)
                self.assertEqual(
                    'initialShadingGroup_lambert'
                    in childrenNames(proxyShape2Children), done)
                self.assertEqual(
                    'initialShadingGroup_lambert'
                    in childrenNames(shadingGroup2Children), not done)

            checkUnparent(done=False)

            # Use relative parenting, else trying to keep absolute world
            # position of Lambert node fails (of course).
            cmds.parent(cubePathStr1, lambertPathStr2, w=True, r=True)
            checkUnparent(done=True)

            cmds.undo()
            checkUnparent(done=False)

            cmds.redo()
            checkUnparent(done=True)

    def testParentingToGPrim(self):
        '''Parenting an object to UsdGeomGprim object is not allowed'''

        # open tree scene
        mayaUtils.openTreeScene()

        with self.assertRaises(RuntimeError):
            cmds.parent(
                "|Tree_usd|Tree_usdShape,/TreeBase/trunk",
                "|Tree_usd|Tree_usdShape,/TreeBase/leavesXform/leaves")
예제 #9
0
class GroupCmdTestCase(unittest.TestCase):
    '''Verify the Maya group command, for multiple runtimes.

    As of 19-Nov-2018, the UFE group command is not integrated into Maya, so
    directly test the UFE undoable command.
    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        standalone.uninitialize()

    def setUp(self):
        ''' Called initially to set up the Maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Open ballset.ma scene in testSamples
        mayaUtils.openGroupBallsScene()

        # Clear selection to start off
        cmds.select(clear=True)

    def testUsdGroup(self):
        '''Creation of USD group objects.'''

        mayaPathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        usdSegmentBall5 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_5")
        ball5Path = ufe.Path([mayaPathSegment, usdSegmentBall5])
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        usdSegmentBall3 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_3")
        ball3Path = ufe.Path([mayaPathSegment, usdSegmentBall3])
        ball3Item = ufe.Hierarchy.createItem(ball3Path)

        usdSegmentProps = usdUtils.createUfePathSegment("/Ball_set/Props")
        parentPath = ufe.Path([mayaPathSegment, usdSegmentProps])
        parentItem = ufe.Hierarchy.createItem(parentPath)

        parentHierarchy = ufe.Hierarchy.hierarchy(parentItem)
        parentChildrenPre = parentHierarchy.children()
        self.assertEqual(len(parentChildrenPre), 6)

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

        # set the edit target to balls.usda
        layer = stage.GetLayerStack()[1]
        self.assertEqual("ballset.usda", layer.GetDisplayName())
        stage.SetEditTarget(layer)

        if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') > '3000'):

            globalSn = ufe.GlobalSelection.get()
            globalSn.append(ball5Item)
            globalSn.append(ball3Item)

            # group
            groupName = cmds.group(ufe.PathString.string(ball5Path),
                                   ufe.PathString.string(ball3Path),
                                   n="newGroup")
        else:
            newGroupName = ufe.PathComponent("newGroup")

            ufeSelectionList = ufe.Selection()
            ufeSelectionList.append(ball5Item)
            ufeSelectionList.append(ball3Item)

            groupCmd = parentHierarchy.createGroupCmd(ufeSelectionList,
                                                      newGroupName)
            groupCmd.execute()

        # Group object (a.k.a parent) will be added to selection list. This behavior matches the native Maya group command.
        globalSelection = ufe.GlobalSelection.get()

        groupPath = ufe.Path([
            mayaPathSegment,
            usdUtils.createUfePathSegment("/Ball_set/Props/newGroup1")
        ])
        self.assertEqual(globalSelection.front(),
                         ufe.Hierarchy.createItem(groupPath))

        # Group object (a.k.a parent) will be added to selection list. This behavior matches the native Maya group command.
        globalSelection = ufe.GlobalSelection.get()

        groupPath = ufe.Path([
            mayaPathSegment,
            usdUtils.createUfePathSegment("/Ball_set/Props/newGroup1")
        ])
        self.assertEqual(globalSelection.front(),
                         ufe.Hierarchy.createItem(groupPath))

        parentChildrenPost = parentHierarchy.children()
        self.assertEqual(len(parentChildrenPost), 5)

        # The command will now append a number 1 at the end to match the naming
        # convention in Maya.
        if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') > '3000'):
            newGroupPath = parentPath + ufe.PathComponent(groupName)
        else:
            newGroupPath = parentPath + ufe.PathComponent("newGroup1")

        # Make sure the new group item has the correct Usd type
        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)
        newGroupType = newGroupPrim.GetTypeName()
        self.assertEqual(newGroupType, 'Xform')

        childPaths = set([child.path() for child in parentChildrenPost])

        self.assertTrue(newGroupPath in childPaths)
        self.assertTrue(ball5Path not in childPaths)
        self.assertTrue(ball3Path not in childPaths)

        if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') > '3000'):
            cmds.undo()
        else:
            groupCmd.undo()

        # global selection should not be empty after undo.
        if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') > '3004'):
            self.assertEqual(len(globalSelection), 2)
        else:
            self.assertEqual(len(globalSelection), 1)

        parentChildrenUndo = parentHierarchy.children()
        self.assertEqual(len(parentChildrenUndo), 6)

        childPathsUndo = set([child.path() for child in parentChildrenUndo])
        self.assertTrue(newGroupPath not in childPathsUndo)
        self.assertTrue(ball5Path in childPathsUndo)
        self.assertTrue(ball3Path in childPathsUndo)

        if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') > '3000'):
            cmds.redo()
        else:
            groupCmd.redo()

        # global selection should still have the group path.
        self.assertEqual(globalSelection.front(),
                         ufe.Hierarchy.createItem(groupPath))

        parentChildrenRedo = parentHierarchy.children()
        self.assertEqual(len(parentChildrenRedo), 5)

        childPathsRedo = set([child.path() for child in parentChildrenRedo])
        self.assertTrue(newGroupPath in childPathsRedo)
        self.assertTrue(ball5Path not in childPathsRedo)
        self.assertTrue(ball3Path not in childPathsRedo)

    @unittest.skipUnless(
        mayaUtils.mayaMajorVersion() >= 2022,
        'Requires Maya fixes only available in Maya 2022 or greater.')
    def testGroupAcrossProxies(self):
        sphereFile = testUtils.getTestScene("groupCmd", "sphere.usda")
        sphereDagPath, sphereStage = mayaUtils.createProxyFromFile(sphereFile)
        usdSphere = sphereDagPath + ",/pSphere1"

        torusFile = testUtils.getTestScene("groupCmd", "torus.usda")
        torusDagPath, torusStage = mayaUtils.createProxyFromFile(torusFile)
        usdTorus = torusDagPath + ",/pTorus1"

        try:
            cmds.group(usdSphere, usdTorus)
        except Exception as e:
            self.assertTrue('cannot group across usd proxies')

    def testGroupKind(self):
        """
        Tests that grouping maintains a contiguous model hierarchy when the
        parent of the group is in the model hierarchy.
        """
        mayaPathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        usdSegmentBall3 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_3")
        ball3Path = ufe.Path([mayaPathSegment, usdSegmentBall3])
        ball3Item = ufe.Hierarchy.createItem(ball3Path)

        usdSegmentBall5 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_5")
        ball5Path = ufe.Path([mayaPathSegment, usdSegmentBall5])
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        usdSegmentProps = usdUtils.createUfePathSegment("/Ball_set/Props")
        propsPath = ufe.Path([mayaPathSegment, usdSegmentProps])
        propsItem = ufe.Hierarchy.createItem(propsPath)

        ufeSelection = ufe.GlobalSelection.get()
        ufeSelection.append(ball3Item)
        ufeSelection.append(ball5Item)

        groupName = "newGroup"
        cmds.group(name=groupName)

        newGroupPath = propsPath + ufe.PathComponent("%s1" % groupName)
        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)

        # The "Props" prim that was the parent of both "Ball" prims has
        # kind=group and is part of the model hierarchy, so the new group prim
        # should also have kind=group and be included in the model hierarchy.
        self.assertEqual(
            Usd.ModelAPI(newGroupPrim).GetKind(), Kind.Tokens.group)
        self.assertTrue(newGroupPrim.IsModel())

        cmds.undo()

        # Clear the kind metadata on the "Props" prim before testing again.
        propsPrim = usdUtils.getPrimFromSceneItem(propsItem)
        Usd.ModelAPI(propsPrim).SetKind("")
        self.assertFalse(propsPrim.IsModel())

        # Freshen the UFE scene items so they have valid handles to their
        # UsdPrims.
        ball3Item = ufe.Hierarchy.createItem(ball3Path)
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        ufeSelection.clear()
        ufeSelection.append(ball3Item)
        ufeSelection.append(ball5Item)

        cmds.group(name=groupName)

        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)

        # When the "Props" prim does not have an authored kind and is not part
        # of the model hierarchy, the new group prim should not have any kind
        # authored either.
        self.assertEqual(Usd.ModelAPI(newGroupPrim).GetKind(), "")
        self.assertFalse(newGroupPrim.IsModel())

    @unittest.skipUnless(
        mayaUtils.mayaMajorVersion() >= 2022,
        'Grouping restriction is only available in Maya 2022 or greater.')
    def testGroupRestriction(self):
        ''' Verify group restriction. '''
        mayaPathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        usdSegmentBall3 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_3")
        ball3Path = ufe.Path([mayaPathSegment, usdSegmentBall3])
        ball3Item = ufe.Hierarchy.createItem(ball3Path)

        usdSegmentBall5 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_5")
        ball5Path = ufe.Path([mayaPathSegment, usdSegmentBall5])
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        usdSegmentProps = usdUtils.createUfePathSegment("/Ball_set/Props")
        propsPath = ufe.Path([mayaPathSegment, usdSegmentProps])
        propsItem = ufe.Hierarchy.createItem(propsPath)

        ufeSelection = ufe.GlobalSelection.get()
        ufeSelection.append(ball3Item)
        ufeSelection.append(ball5Item)

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

        # set the edit target to the session layer
        stage.SetEditTarget(stage.GetSessionLayer())

        # expect the exception happens
        with self.assertRaises(RuntimeError):
            cmds.group(name="newGroup")

        # set the edit target to the root layer
        stage.SetEditTarget(stage.GetRootLayer())

        # create a sphere
        stage.DefinePrim('/Sphere1', 'Sphere')

        # select the Sphere
        spherePath = ufe.PathString.path(
            "{},/Sphere1".format("|transform1|proxyShape1"))
        sphereItem = ufe.Hierarchy.createItem(spherePath)
        ufeSelection = ufe.GlobalSelection.get()
        ufeSelection.clear()
        ufeSelection.append(sphereItem)

        # set the edit target to the session layer
        stage.SetEditTarget(stage.GetSessionLayer())

        # expect the exception happens.
        with self.assertRaises(RuntimeError):
            cmds.group(name="newGroup")

        # undo
        cmds.undo()

        # verify that group1 doesn't exist after the undo
        self.assertEqual([item for item in stage.Traverse()], [
            stage.GetPrimAtPath("/Ball_set"),
            stage.GetPrimAtPath("/Ball_set/Props"),
            stage.GetPrimAtPath("/Ball_set/Props/Ball_1"),
            stage.GetPrimAtPath("/Ball_set/Props/Ball_2"),
            stage.GetPrimAtPath("/Ball_set/Props/Ball_3"),
            stage.GetPrimAtPath("/Ball_set/Props/Ball_4"),
            stage.GetPrimAtPath("/Ball_set/Props/Ball_5"),
            stage.GetPrimAtPath("/Ball_set/Props/Ball_6"),
            stage.GetPrimAtPath("/Sphere1")
        ])

    @unittest.skipIf(
        os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '3005',
        'testGroupAbsolute is only available in UFE preview version 0.3.5 and greater'
    )
    def testGroupAbsolute(self):
        '''Verify -absolute flag.'''

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

        # create a stage
        (stage, proxyShapePathStr, proxyShapeItem, contextOp) = createStage()

        # create a sphere generator
        sphereGen = SphereGenerator(1, contextOp, proxyShapePathStr)

        spherePath = sphereGen.createSphere()
        spherePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(spherePath))

        # no TRS attributes
        self.assertFalse(spherePrim.HasAttribute('xformOp:translate'))
        self.assertFalse(spherePrim.HasAttribute('xformOp:rotateXYZ'))
        self.assertFalse(spherePrim.HasAttribute('xformOp:scale'))

        # create a group with absolute flag set to True
        cmds.group(ufe.PathString.string(spherePath), absolute=True)

        # verify that groupItem has 1 child
        groupItem = ufe.GlobalSelection.get().front()
        groupHierarchy = ufe.Hierarchy.hierarchy(groupItem)
        self.assertEqual(len(groupHierarchy.children()), 1)

        # verify XformOpOrderAttr exist after grouping
        newspherePrim = stage.GetPrimAtPath("/group1/Sphere1")
        sphereXformable = UsdGeom.Xformable(newspherePrim)

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

    @unittest.skipIf(
        os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '3005',
        'testGroupRelative is only available in UFE preview version 0.3.5 and greater'
    )
    def testGroupRelative(self):
        '''Verify -relative flag.'''
        cmds.file(new=True, force=True)

        # create a stage
        (stage, proxyShapePathStr, proxyShapeItem, contextOp) = createStage()

        # create a sphere generator
        sphereGen = SphereGenerator(1, contextOp, proxyShapePathStr)

        spherePath = sphereGen.createSphere()
        spherePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(spherePath))

        # no TRS attributes
        self.assertFalse(spherePrim.HasAttribute('xformOp:translate'))
        self.assertFalse(spherePrim.HasAttribute('xformOp:rotateXYZ'))
        self.assertFalse(spherePrim.HasAttribute('xformOp:scale'))

        # create a group with relative flag set to True
        cmds.group(ufe.PathString.string(spherePath), relative=True)

        # verify that groupItem has 1 child
        groupItem = ufe.GlobalSelection.get().front()
        groupHierarchy = ufe.Hierarchy.hierarchy(groupItem)
        self.assertEqual(len(groupHierarchy.children()), 1)

        # verify XformOpOrderAttr exist after grouping
        newspherePrim = stage.GetPrimAtPath("/group1/Sphere1")

        # no TRS attributes
        self.assertFalse(newspherePrim.HasAttribute('xformOp:translate'))
        self.assertFalse(newspherePrim.HasAttribute('xformOp:rotateXYZ'))
        self.assertFalse(newspherePrim.HasAttribute('xformOp:scale'))

    @unittest.skipIf(
        os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '3005',
        'testGroupWorld is only available in UFE preview version 0.3.5 and greater'
    )
    def testGroupWorld(self):
        '''Verify -world flag.'''
        cmds.file(new=True, force=True)

        # create a stage
        (stage, proxyShapePathStr, proxyShapeItem, contextOp) = createStage()

        # create a sphere generator
        sphereGen = SphereGenerator(3, contextOp, proxyShapePathStr)

        sphere1Path = sphereGen.createSphere()
        sphere1Prim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(sphere1Path))

        sphere2Path = sphereGen.createSphere()
        sphere2Prim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(sphere2Path))

        sphere3Path = sphereGen.createSphere()
        sphere3Prim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(sphere3Path))

        # group Sphere1, Sphere2, and Sphere3
        groupName = cmds.group(ufe.PathString.string(sphere1Path),
                               ufe.PathString.string(sphere2Path),
                               ufe.PathString.string(sphere3Path))

        # verify that groupItem has 3 children
        groupItem = ufe.GlobalSelection.get().front()
        groupHierarchy = ufe.Hierarchy.hierarchy(groupItem)
        self.assertEqual(len(groupHierarchy.children()), 3)

        # group Sphere2 and Sphere3 with world flag enabled.
        # world flag puts the new group under the world
        cmds.group("{},/group1/Sphere2".format(proxyShapePathStr),
                   "{},/group1/Sphere3".format(proxyShapePathStr),
                   world=True)

        # verify group2 was created under the proxyshape
        self.assertEqual([item for item in stage.Traverse()], [
            stage.GetPrimAtPath("/group1"),
            stage.GetPrimAtPath("/group1/Sphere1"),
            stage.GetPrimAtPath("/group2"),
            stage.GetPrimAtPath("/group2/Sphere2"),
            stage.GetPrimAtPath("/group2/Sphere3")
        ])

    @unittest.skipIf(
        os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '3005',
        'testGroupUndoRedo is only available in UFE preview version 0.3.5 and greater'
    )
    def testGroupUndoRedo(self):
        '''Verify grouping after multiple undo/redo.'''
        cmds.file(new=True, force=True)

        # create a stage
        (stage, proxyShapePathStr, proxyShapeItem, contextOp) = createStage()

        # create a sphere generator
        sphereGen = SphereGenerator(3, contextOp, proxyShapePathStr)

        sphere1Path = sphereGen.createSphere()
        sphere1Prim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(sphere1Path))

        sphere2Path = sphereGen.createSphere()
        sphere2Prim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(sphere2Path))

        sphere3Path = sphereGen.createSphere()
        sphere3Prim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(sphere3Path))

        # group Sphere1, Sphere2, and Sphere3
        groupName = cmds.group(ufe.PathString.string(sphere1Path),
                               ufe.PathString.string(sphere2Path),
                               ufe.PathString.string(sphere3Path))

        # verify that groupItem has 3 children
        groupItem = ufe.GlobalSelection.get().front()
        groupHierarchy = ufe.Hierarchy.hierarchy(groupItem)
        self.assertEqual(len(groupHierarchy.children()), 3)

        cmds.undo()

        self.assertEqual([item for item in stage.Traverse()], [
            stage.GetPrimAtPath("/Sphere3"),
            stage.GetPrimAtPath("/Sphere2"),
            stage.GetPrimAtPath("/Sphere1")
        ])

        cmds.redo()

        self.assertEqual([item for item in stage.Traverse()], [
            stage.GetPrimAtPath("/group1"),
            stage.GetPrimAtPath("/group1/Sphere1"),
            stage.GetPrimAtPath("/group1/Sphere2"),
            stage.GetPrimAtPath("/group1/Sphere3")
        ])

    @unittest.skipIf(
        mayaUtils.previewReleaseVersion() < 128,
        'Test requires fix in Maya Preview Release 128 or greater.')
    def testGroupHierarchy(self):
        '''Grouping a node and a descendant.'''
        # MAYA-112957: when grouping a node and its descendant, with the node
        # selected first, the descendant path becomes stale as soon as its
        # ancestor gets reparented.  The Maya parent command must deal with
        # this.  A similar test is done for parenting in testParentCmd.py

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

        # Create the following hierarchy:
        #
        # ps
        #  |_ A
        #      |_ B
        #          |_ C
        #  |_ D
        #      |_ E
        #          |_ F
        #
        #  |_ G
        #
        # We will select A, B, C, E, F and G, in order, and group.  This will
        # create a new group under ps, with A, B, C, E, F and G as children.

        psPathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        stage = mayaUsd.lib.GetPrim(psPathStr).GetStage()
        stage.DefinePrim('/A', 'Xform')
        stage.DefinePrim('/A/B', 'Xform')
        stage.DefinePrim('/A/B/C', 'Xform')
        stage.DefinePrim('/D', 'Xform')
        stage.DefinePrim('/D/E', 'Xform')
        stage.DefinePrim('/D/E/F', 'Xform')
        stage.DefinePrim('/G', 'Xform')

        psPath = ufe.PathString.path(psPathStr)
        psPathSegment = psPath.segments[0]
        ps = ufe.Hierarchy.createItem(psPath)
        psHier = ufe.Hierarchy.hierarchy(ps)
        dPath = ufe.Path([psPathSegment, usdUtils.createUfePathSegment('/D')])
        d = ufe.Hierarchy.createItem(dPath)
        dHier = ufe.Hierarchy.hierarchy(d)
        groupPath = ufe.Path(
            [psPathSegment,
             usdUtils.createUfePathSegment('/group1')])

        def hierarchyBefore():
            aPath = ufe.Path(
                [psPathSegment,
                 usdUtils.createUfePathSegment('/A')])
            a = ufe.Hierarchy.createItem(aPath)
            bPath = aPath + ufe.PathComponent('B')
            b = ufe.Hierarchy.createItem(bPath)
            cPath = bPath + ufe.PathComponent('C')
            c = ufe.Hierarchy.createItem(cPath)
            ePath = dPath + ufe.PathComponent('E')
            e = ufe.Hierarchy.createItem(ePath)
            fPath = ePath + ufe.PathComponent('F')
            f = ufe.Hierarchy.createItem(fPath)
            gPath = ufe.Path(
                [psPathSegment,
                 usdUtils.createUfePathSegment('/G')])
            g = ufe.Hierarchy.createItem(gPath)
            return [a, b, c, e, f, g]

        def hierarchyAfter():
            return [
                ufe.Hierarchy.createItem(groupPath + ufe.PathComponent(pc))
                for pc in ['A', 'B', 'C', 'E', 'F', 'G']
            ]

        def checkBefore(a, b, c, e, f, g):
            psChildren = psHier.children()
            aHier = ufe.Hierarchy.hierarchy(a)
            bHier = ufe.Hierarchy.hierarchy(b)
            cHier = ufe.Hierarchy.hierarchy(c)
            eHier = ufe.Hierarchy.hierarchy(e)
            fHier = ufe.Hierarchy.hierarchy(f)
            gHier = ufe.Hierarchy.hierarchy(g)

            self.assertIn(a, psChildren)
            self.assertIn(d, psChildren)
            self.assertIn(g, psChildren)
            self.assertIn(b, aHier.children())
            self.assertIn(c, bHier.children())
            self.assertIn(e, dHier.children())
            self.assertIn(f, eHier.children())
            self.assertFalse(gHier.hasChildren())

        def checkAfter(a, b, c, e, f, g):
            psChildren = psHier.children()
            self.assertNotIn(a, psChildren)
            self.assertIn(d, psChildren)
            self.assertNotIn(g, psChildren)

            group = ufe.Hierarchy.createItem(groupPath)
            groupHier = ufe.Hierarchy.hierarchy(group)
            groupChildren = groupHier.children()

            for child in [a, b, c, e, f, g]:
                hier = ufe.Hierarchy.hierarchy(child)
                self.assertFalse(hier.hasChildren())
                self.assertIn(child, groupChildren)

        children = hierarchyBefore()
        checkBefore(*children)

        sn = ufe.GlobalSelection.get()
        sn.clear()
        for child in children:
            sn.append(child)

        cmds.group()

        children = hierarchyAfter()
        checkAfter(*children)

        cmds.undo()

        children = hierarchyBefore()
        checkBefore(*children)

        cmds.redo()

        children = hierarchyAfter()
        checkAfter(*children)

    @unittest.skipIf(
        mayaUtils.previewReleaseVersion() < 128,
        'Test requires fix in Maya Preview Release 128 or greater.')
    def testGroupHierarchyWithRenaming(self):
        '''Grouping a node and a descendant when all descendants share the same name'''
        # MAYA-113532: when grouping a node and its descendant sharing the same name, we need to
        # detect the name conflicts and rename as we reparent into the group.

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

        # Create the following hierarchy:
        #
        # ps
        #  |_ A
        #      |_ A
        #          |_ A
        #              |_ A
        #                  |_ A
        #
        # And group them all at the same level, which implies renaming.

        psPathStr = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        stage = mayaUsd.lib.GetPrim(psPathStr).GetStage()
        stage.DefinePrim('/A', 'Xform')
        stage.DefinePrim('/A/B', 'Cube')
        stage.DefinePrim('/A/A', 'Xform')
        stage.DefinePrim('/A/A/C', 'Cone')
        stage.DefinePrim('/A/A/A', 'Xform')
        stage.DefinePrim('/A/A/A/D', 'Sphere')
        stage.DefinePrim('/A/A/A/A', 'Xform')
        stage.DefinePrim('/A/A/A/A/E', 'Capsule')
        stage.DefinePrim('/A/A/A/A/A', 'Xform')
        stage.DefinePrim('/A/A/A/A/A/F', 'Cylinder')

        psPath = ufe.PathString.path(psPathStr)
        psPathSegment = psPath.segments[0]
        ps = ufe.Hierarchy.createItem(psPath)
        psHier = ufe.Hierarchy.hierarchy(ps)
        groupPath = ufe.Path(
            [psPathSegment,
             usdUtils.createUfePathSegment('/group1')])

        def hierarchyBefore():
            retVal = []
            aPath = ufe.Path(
                [psPathSegment,
                 usdUtils.createUfePathSegment('/A')])
            retVal.append(ufe.Hierarchy.createItem(aPath))
            for i in range(4):
                aPath = aPath + ufe.PathComponent('A')
                retVal.append(ufe.Hierarchy.createItem(aPath))
            return retVal

        a, aa, aaa, aaaa, aaaaa = hierarchyBefore()

        def checkBefore():
            # We care about 2 things:
            #  - All Xforms are named A
            #  - The non-xform stay in the right order:
            order = [("B", "Cube"), ("C", "Cone"), ("D", "Sphere"),
                     ("E", "Capsule"), ("F", "Cylinder")]
            psChildren = psHier.children()
            self.assertEqual(len(psChildren), 1)
            a = psChildren[0]
            for gprimName, gprimTypeName in order:
                aHier = ufe.Hierarchy.hierarchy(a)
                aChildren = aHier.children()
                if gprimName == "F":
                    self.assertEqual(len(aChildren), 1)
                else:
                    self.assertEqual(len(aChildren), 2)
                for child in aChildren:
                    prim = mayaUsd.ufe.ufePathToPrim(
                        ufe.PathString.string(child.path()))
                    if child.nodeName() == "A":
                        self.assertEqual(prim.GetTypeName(), "Xform")
                        a = child
                    else:
                        self.assertEqual(child.nodeName(), gprimName)
                        self.assertEqual(prim.GetTypeName(), gprimTypeName)

        def checkAfter():
            # We care about 2 things:
            #  - Group has 5 Xform children. Don't care about names: they will be unique.
            #  - Each child has a one of the non-xform prims.
            members = set([("B", "Cube"), ("C", "Cone"), ("D", "Sphere"),
                           ("E", "Capsule"), ("F", "Cylinder")])
            group = ufe.Hierarchy.createItem(groupPath)
            groupHier = ufe.Hierarchy.hierarchy(group)
            groupChildren = groupHier.children()
            self.assertEqual(len(groupChildren), 5)
            foundMembers = set()
            for child in groupChildren:
                prim = mayaUsd.ufe.ufePathToPrim(
                    ufe.PathString.string(child.path()))
                self.assertEqual(prim.GetTypeName(), "Xform")
                childHier = ufe.Hierarchy.hierarchy(child)
                childChildren = childHier.children()
                self.assertEqual(len(childChildren), 1)
                member = childChildren[0]
                prim = mayaUsd.ufe.ufePathToPrim(
                    ufe.PathString.string(member.path()))
                foundMembers.add((member.nodeName(), prim.GetTypeName()))
            self.assertEqual(members, foundMembers)

        sn = ufe.GlobalSelection.get()
        sn.clear()
        # We randomize the order a bit to make sure previously moved items do
        # not affect the next one.
        sn.append(a)
        sn.append(aaa)
        sn.append(aa)
        sn.append(aaaaa)
        sn.append(aaaa)

        cmds.group()
        checkAfter()
        cmds.undo()
        checkBefore()
        cmds.redo()
        checkAfter()
예제 #10
0
class GroupCmdTestCase(unittest.TestCase):
    '''Verify the Maya group command, for multiple runtimes.

    As of 19-Nov-2018, the UFE group command is not integrated into Maya, so
    directly test the UFE undoable command.
    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    def setUp(self):
        ''' Called initially to set up the Maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Open ballset.ma scene in testSamples
        mayaUtils.openGroupBallsScene()

        # Clear selection to start off
        cmds.select(clear=True)

    def testUsdGroup(self):
        '''Creation of USD group objects.'''

        mayaPathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        usdSegmentBall5 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_5")
        ball5Path = ufe.Path([mayaPathSegment, usdSegmentBall5])
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        usdSegmentBall3 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_3")
        ball3Path = ufe.Path([mayaPathSegment, usdSegmentBall3])
        ball3Item = ufe.Hierarchy.createItem(ball3Path)

        usdSegmentProps = usdUtils.createUfePathSegment("/Ball_set/Props")
        parentPath = ufe.Path([mayaPathSegment, usdSegmentProps])
        parentItem = ufe.Hierarchy.createItem(parentPath)

        parentHierarchy = ufe.Hierarchy.hierarchy(parentItem)
        parentChildrenPre = parentHierarchy.children()
        self.assertEqual(len(parentChildrenPre), 6)

        newGroupName = ufe.PathComponent("newGroup")

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

        # set the edit target to balls.usda
        layer = stage.GetLayerStack()[1]
        self.assertEqual("ballset.usda", layer.GetDisplayName())
        stage.SetEditTarget(layer)

        ufeSelectionList = ufe.Selection()
        ufeSelectionList.append(ball5Item)
        ufeSelectionList.append(ball3Item)

        groupCmd = parentHierarchy.createGroupCmd(ufeSelectionList,
                                                  newGroupName)
        groupCmd.execute()

        parentChildrenPost = parentHierarchy.children()
        self.assertEqual(len(parentChildrenPost), 5)

        # The command will now append a number 1 at the end to match the naming
        # convention in Maya.
        newGroupPath = parentPath + ufe.PathComponent("newGroup1")

        # Make sure the new group item has the correct Usd type
        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)
        newGroupType = newGroupPrim.GetTypeName()
        self.assertEqual(newGroupType, 'Xform')

        childPaths = set([child.path() for child in parentChildrenPost])

        self.assertTrue(newGroupPath in childPaths)
        self.assertTrue(ball5Path not in childPaths)
        self.assertTrue(ball3Path not in childPaths)

        groupCmd.undo()

        parentChildrenUndo = parentHierarchy.children()
        self.assertEqual(len(parentChildrenUndo), 6)

        childPathsUndo = set([child.path() for child in parentChildrenUndo])
        self.assertTrue(newGroupPath not in childPathsUndo)
        self.assertTrue(ball5Path in childPathsUndo)
        self.assertTrue(ball3Path in childPathsUndo)

        groupCmd.redo()

        parentChildrenRedo = parentHierarchy.children()
        self.assertEqual(len(parentChildrenRedo), 5)

        childPathsRedo = set([child.path() for child in parentChildrenRedo])
        self.assertTrue(newGroupPath in childPathsRedo)
        self.assertTrue(ball5Path not in childPathsRedo)
        self.assertTrue(ball3Path not in childPathsRedo)

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 121,
        'Requires Maya fixes only available in Maya Preview Release 121 or later.'
    )
    def testGroupAcrossProxies(self):
        sphereFile = mayaUtils.getTestScene("groupCmd", "sphere.usda")
        sphereDagPath, sphereStage = mayaUtils.createProxyFromFile(sphereFile)
        usdSphere = sphereDagPath + ",/pSphere1"

        torusFile = mayaUtils.getTestScene("groupCmd", "torus.usda")
        torusDagPath, torusStage = mayaUtils.createProxyFromFile(torusFile)
        usdTorus = torusDagPath + ",/pTorus1"

        try:
            cmds.group(usdSphere, usdTorus)
        except Exception as e:
            self.assertTrue('cannot group across usd proxies')
예제 #11
0
class SelectTestCase(unittest.TestCase):
    '''Verify UFE selection on a USD scene.'''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        cmds.file(new=True, force=True)

    def setUp(self):
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Load a file that has the same scene in both the Maya Dag
        # hierarchy and the USD hierarchy.
        mayaUtils.openTestScene("parentCmd", "simpleSceneMayaPlusUSD_TRS.ma")

        # Create multiple scene items.  We will alternate between selecting a
        # Maya item and a USD item, 3 items each data model, one item at a
        # time, so we select 6 different items, one at a time.
        shapeSegment = mayaUtils.createUfePathSegment(
            "|mayaUsdProxy1|mayaUsdProxyShape1")
        ufeNames = ["cubeXform", "cylinderXform", "sphereXform"]
        mayaNames = ["pCube1", "pCylinder1", "pSphere1"]
        usdPaths = []
        for n in ["/" + o for o in ufeNames]:
            usdPaths.append(
                ufe.Path([shapeSegment,
                          usdUtils.createUfePathSegment(n)]))
        mayaPaths = []
        for n in ["|" + o for o in mayaNames]:
            mayaPaths.append(ufe.Path(mayaUtils.createUfePathSegment(n)))

        # Create a list of paths by alternating USD objects and Maya objects
        # Flatten zipped tuples using list comprehension double loop.
        zipped = zip(usdPaths, mayaPaths)
        paths = [j for i in zipped for j in i]

        # Create items for all paths.
        self.items = [ufe.Hierarchy.createItem(p) for p in paths]

        # Clear selection to start off
        cmds.select(clear=True)

    def runTestSelection(self, selectCmd):
        '''Run the replace selection test, using the argument command to
        replace the selection with a single scene item.'''

        # Clear the selection.
        globalSn = ufe.GlobalSelection.get()
        globalSn.clear()
        self.assertTrue(globalSn.empty())

        # Select all items in turn.
        for item in self.items:
            selectCmd(item)

        # Item in the selection should be the last item in our list.
        self.assertEqual(len(globalSn), 1)

        def snFront(sn):
            return next(iter(sn)) if \
                ufe.VersionInfo.getMajorVersion() == 1 else sn.front()

        self.assertEqual(snFront(globalSn), self.items[-1])

        # Check undo.  For this purpose, re-create the list of items in reverse
        # order.  Because we're already at the last item, we skip the last one
        # (i.e. last item is -1, so start at -2, and increment by -1).
        rItems = self.items[-2::-1]

        # Undo until the first element, checking the selection as we go.
        for i in rItems:
            cmds.undo()
            self.assertEqual(len(globalSn), 1)
            self.assertEqual(snFront(globalSn), i)

        # Check redo.
        fItems = self.items[1:]

        # Redo until the last element, checking the selection as we go.
        for i in fItems:
            cmds.redo()
            self.assertEqual(len(globalSn), 1)
            self.assertEqual(snFront(globalSn), i)

    def testUfeSelect(self):
        def selectCmd(item):
            sn = ufe.Selection()
            sn.append(item)
            ufeSelectCmd.replaceWith(sn)

        self.runTestSelection(selectCmd)

    @unittest.skipUnless((
        (ufeUtils.ufeFeatureSetVersion() >= 2) and
        (mayaUtils.previewReleaseVersion() >= 121)
    ), 'testMayaSelect only available in UFE v2 or greater and Maya Preview Release 121 or later.'
                         )
    def testMayaSelect(self):
        # Maya PR 121 now has support for UFE path string in select command.
        def selectCmd(item):
            cmds.select(ufe.PathString.string(item.path()))

        self.runTestSelection(selectCmd)

    @unittest.skipUnless((
        (ufeUtils.ufeFeatureSetVersion() >= 2) and
        (mayaUtils.previewReleaseVersion() >= 121)
    ), 'testMayaSelectFlags only available in UFE v2 or greater and Maya Preview Release 121 or later.'
                         )
    def testMayaSelectFlags(self):
        # Maya PR 121 now has support for UFE path string in select command.

        # Clear the selection.
        globalSn = ufe.GlobalSelection.get()
        globalSn.clear()
        self.assertTrue(globalSn.empty())

        # Incrementally add to the selection all items in turn.
        # Also testing undo/redo along the way.
        cnt = 0
        for item in self.items:
            cnt += 1
            cmds.select(ufe.PathString.string(item.path()), add=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt - 1, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

        # Since we added all the items to the global selection, it
        # should have all of them.
        self.assertEqual(len(globalSn), len(self.items))

        # Ensure the global selection order is the same as our item order.
        itemIt = iter(self.items)
        for selIt in globalSn:
            self.assertEqual(selIt, next(itemIt))

        # Incrementally remove from the selection all items in turn.
        # Also testing undo/redo along the way.
        for item in self.items:
            cnt -= 1
            cmds.select(ufe.PathString.string(item.path()), deselect=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt + 1, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

        # Since we removed all items from global selection it
        # should be empty now.
        self.assertTrue(globalSn.empty())

        # Incrementally toggle selection state of all items in turn.
        # Since they all start unselected, they will toggle to selected.
        # Also testing undo/redo along the way.
        globalSn.clear()
        self.assertTrue(globalSn.empty())
        cnt = 0
        for item in self.items:
            cnt += 1
            cmds.select(ufe.PathString.string(item.path()), toggle=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt - 1, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

        # Since we toggled each item to selected, we should have all
        # of them on the global selection.
        self.assertEqual(len(globalSn), len(self.items))

        # Incrementally toggle selection state of all items in turn.
        # Since they all start selected, they will toggle to unselected.
        # Also testing undo/redo along the way.
        for item in self.items:
            cnt -= 1
            cmds.select(ufe.PathString.string(item.path()), toggle=True)
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

            cmds.undo()
            self.assertEqual(cnt + 1, len(globalSn))
            self.assertTrue(globalSn.contains(item.path()))

            cmds.redo()
            self.assertEqual(cnt, len(globalSn))
            self.assertFalse(globalSn.contains(item.path()))

        # Since we toggled all items to unselected, the global selection
        # should be empty now.
        self.assertTrue(globalSn.empty())

        # Select all the items at once, replacing the existing selection.
        # Also testing undo/redo.
        pathStrings = [ufe.PathString.string(i.path()) for i in self.items]
        cmds.select(*pathStrings, replace=True)
        self.assertEqual(len(globalSn), len(self.items))

        # Ensure the global selection order is the same as our item order.
        itemIt = iter(self.items)
        for selIt in globalSn:
            self.assertEqual(selIt, next(itemIt))

        cmds.undo()
        self.assertEqual(0, len(globalSn))
        self.assertTrue(globalSn.empty())

        cmds.redo()
        self.assertEqual(len(globalSn), len(self.items))

        # Ensure the global selection order is the same as our item order.
        itemIt = iter(self.items)
        for selIt in globalSn:
            self.assertEqual(selIt, next(itemIt))

        # With all items selected (and in same order as item order)
        # "select -add" the first item which will move it to the end.
        first = self.items[0]
        self.assertEqual(globalSn.front(), first)
        cmds.select(ufe.PathString.string(first.path()), add=True)
        self.assertTrue(globalSn.contains(first.path()))
        self.assertEqual(globalSn.back(), first)
예제 #12
0
class GroupCmdTestCase(unittest.TestCase):
    '''Verify the Maya group command, for multiple runtimes.

    As of 19-Nov-2018, the UFE group command is not integrated into Maya, so
    directly test the UFE undoable command.
    '''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        standalone.uninitialize()

    def setUp(self):
        ''' Called initially to set up the Maya test environment '''
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Open ballset.ma scene in testSamples
        mayaUtils.openGroupBallsScene()

        # Clear selection to start off
        cmds.select(clear=True)

    def testUsdGroup(self):
        '''Creation of USD group objects.'''

        mayaPathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        usdSegmentBall5 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_5")
        ball5Path = ufe.Path([mayaPathSegment, usdSegmentBall5])
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        usdSegmentBall3 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_3")
        ball3Path = ufe.Path([mayaPathSegment, usdSegmentBall3])
        ball3Item = ufe.Hierarchy.createItem(ball3Path)

        usdSegmentProps = usdUtils.createUfePathSegment("/Ball_set/Props")
        parentPath = ufe.Path([mayaPathSegment, usdSegmentProps])
        parentItem = ufe.Hierarchy.createItem(parentPath)

        parentHierarchy = ufe.Hierarchy.hierarchy(parentItem)
        parentChildrenPre = parentHierarchy.children()
        self.assertEqual(len(parentChildrenPre), 6)

        newGroupName = ufe.PathComponent("newGroup")

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

        # set the edit target to balls.usda
        layer = stage.GetLayerStack()[1]
        self.assertEqual("ballset.usda", layer.GetDisplayName())
        stage.SetEditTarget(layer)

        ufeSelectionList = ufe.Selection()
        ufeSelectionList.append(ball5Item)
        ufeSelectionList.append(ball3Item)

        groupCmd = parentHierarchy.createGroupCmd(ufeSelectionList,
                                                  newGroupName)
        groupCmd.execute()

        parentChildrenPost = parentHierarchy.children()
        self.assertEqual(len(parentChildrenPost), 5)

        # The command will now append a number 1 at the end to match the naming
        # convention in Maya.
        newGroupPath = parentPath + ufe.PathComponent("newGroup1")

        # Make sure the new group item has the correct Usd type
        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)
        newGroupType = newGroupPrim.GetTypeName()
        self.assertEqual(newGroupType, 'Xform')

        childPaths = set([child.path() for child in parentChildrenPost])

        self.assertTrue(newGroupPath in childPaths)
        self.assertTrue(ball5Path not in childPaths)
        self.assertTrue(ball3Path not in childPaths)

        groupCmd.undo()

        parentChildrenUndo = parentHierarchy.children()
        self.assertEqual(len(parentChildrenUndo), 6)

        childPathsUndo = set([child.path() for child in parentChildrenUndo])
        self.assertTrue(newGroupPath not in childPathsUndo)
        self.assertTrue(ball5Path in childPathsUndo)
        self.assertTrue(ball3Path in childPathsUndo)

        groupCmd.redo()

        parentChildrenRedo = parentHierarchy.children()
        self.assertEqual(len(parentChildrenRedo), 5)

        childPathsRedo = set([child.path() for child in parentChildrenRedo])
        self.assertTrue(newGroupPath in childPathsRedo)
        self.assertTrue(ball5Path not in childPathsRedo)
        self.assertTrue(ball3Path not in childPathsRedo)

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 121,
        'Requires Maya fixes only available in Maya Preview Release 121 or later.'
    )
    def testGroupAcrossProxies(self):
        sphereFile = testUtils.getTestScene("groupCmd", "sphere.usda")
        sphereDagPath, sphereStage = mayaUtils.createProxyFromFile(sphereFile)
        usdSphere = sphereDagPath + ",/pSphere1"

        torusFile = testUtils.getTestScene("groupCmd", "torus.usda")
        torusDagPath, torusStage = mayaUtils.createProxyFromFile(torusFile)
        usdTorus = torusDagPath + ",/pTorus1"

        try:
            cmds.group(usdSphere, usdTorus)
        except Exception as e:
            self.assertTrue('cannot group across usd proxies')

    def testGroupKind(self):
        """
        Tests that grouping maintains a contiguous model hierarchy when the
        parent of the group is in the model hierarchy.
        """
        mayaPathSegment = mayaUtils.createUfePathSegment(
            "|transform1|proxyShape1")

        usdSegmentBall3 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_3")
        ball3Path = ufe.Path([mayaPathSegment, usdSegmentBall3])
        ball3Item = ufe.Hierarchy.createItem(ball3Path)

        usdSegmentBall5 = usdUtils.createUfePathSegment(
            "/Ball_set/Props/Ball_5")
        ball5Path = ufe.Path([mayaPathSegment, usdSegmentBall5])
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        usdSegmentProps = usdUtils.createUfePathSegment("/Ball_set/Props")
        propsPath = ufe.Path([mayaPathSegment, usdSegmentProps])
        propsItem = ufe.Hierarchy.createItem(propsPath)

        ufeSelection = ufe.GlobalSelection.get()
        ufeSelection.append(ball3Item)
        ufeSelection.append(ball5Item)

        groupName = "newGroup"
        cmds.group(name=groupName)

        newGroupPath = propsPath + ufe.PathComponent("%s1" % groupName)
        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)

        # The "Props" prim that was the parent of both "Ball" prims has
        # kind=group and is part of the model hierarchy, so the new group prim
        # should also have kind=group and be included in the model hierarchy.
        self.assertEqual(
            Usd.ModelAPI(newGroupPrim).GetKind(), Kind.Tokens.group)
        self.assertTrue(newGroupPrim.IsModel())

        cmds.undo()

        # Clear the kind metadata on the "Props" prim before testing again.
        propsPrim = usdUtils.getPrimFromSceneItem(propsItem)
        Usd.ModelAPI(propsPrim).SetKind("")
        self.assertFalse(propsPrim.IsModel())

        # Freshen the UFE scene items so they have valid handles to their
        # UsdPrims.
        ball3Item = ufe.Hierarchy.createItem(ball3Path)
        ball5Item = ufe.Hierarchy.createItem(ball5Path)

        ufeSelection.clear()
        ufeSelection.append(ball3Item)
        ufeSelection.append(ball5Item)

        cmds.group(name=groupName)

        newGroupItem = ufe.Hierarchy.createItem(newGroupPath)
        newGroupPrim = usdUtils.getPrimFromSceneItem(newGroupItem)

        # When the "Props" prim does not have an authored kind and is not part
        # of the model hierarchy, the new group prim should not have any kind
        # authored either.
        self.assertEqual(Usd.ModelAPI(newGroupPrim).GetKind(), "")
        self.assertFalse(newGroupPrim.IsModel())
예제 #13
0
class testUsdExportImportRoundtripPreviewSurface(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        cls.inputPath = fixturesUtils.setUpClass(__file__)

        test_dir = os.path.join(cls.inputPath,
                                "UsdExportImportRoundtripPreviewSurface")
        if not os.path.isdir(test_dir):
            os.mkdir(test_dir)
        cmds.workspace(test_dir, o=True)

    @classmethod
    def tearDownClass(cls):
        standalone.uninitialize()

    def testUsdPreviewSurfaceRoundtripSpecular(self):
        self.__testUsdPreviewSurfaceRoundtrip(metallic=False)

    def testUsdPreviewSurfaceRoundtripMetallic(self):
        self.__testUsdPreviewSurfaceRoundtrip(metallic=True)

    @unittest.skipUnless("mayaUtils" in globals() and mayaUtils.previewReleaseVersion() >= 126 and Usd.GetVersion() > (0, 21, 2), 'Requires MaterialX support.')
    def testUsdPreviewSurfaceRoundtripMaterialX(self):
        self.__testUsdPreviewSurfaceRoundtrip(metallic=True,
                                              convertTo="MaterialX")

    def __testUsdPreviewSurfaceRoundtrip(self,
                                         metallic=True,
                                         convertTo="UsdPreviewSurface"):
        """
        Tests that a usdPreviewSurface exports and imports correctly.
        """
        mark = Tf.Error.Mark()
        mark.SetMark()
        self.assertTrue(mark.IsClean())

        mayaUsdPluginName = "mayaUsdPlugin"
        if not cmds.pluginInfo(mayaUsdPluginName, query=True, loaded=True):
            cmds.loadPlugin(mayaUsdPluginName)

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

        sphere_xform = cmds.polySphere()[0]

        material_node = cmds.shadingNode("usdPreviewSurface", asShader=True,
                                         name="usdPreviewSurface42")

        material_sg = cmds.sets(renderable=True, noSurfaceShader=True,
                                empty=True, name=material_node+"SG")
        cmds.connectAttr(material_node+".outColor",
                         material_sg+".surfaceShader", force=True)
        cmds.sets(sphere_xform, e=True, forceElement=material_sg)

        cmds.setAttr(material_node + ".ior", 2)
        cmds.setAttr(material_node + ".roughness", 0.25)
        cmds.setAttr(material_node + ".specularColor", 0.125, 0.25, 0.75,
                     type="double3")
        cmds.setAttr(material_node + ".useSpecularWorkflow", not metallic)
        cmds.setAttr(material_node + ".opacityThreshold", 0.5)

        file_node = cmds.shadingNode("file", asTexture=True,
                                     isColorManaged=True)
        uv_node = cmds.shadingNode("place2dTexture", asUtility=True)

        connectUVNode(uv_node, file_node)

        cmds.connectAttr(file_node + ".outColor",
                         material_node + ".diffuseColor", f=True)

        txfile = os.path.join("..", "textures", "Brazilian_rosewood_pxr128.png")
        cmds.setAttr(file_node+".fileTextureName", txfile, type="string")
        cmds.setAttr(file_node+".colorSpace", "ACEScg", type="string")
        cmds.setAttr(file_node + ".defaultColor", 0.5, 0.25, 0.125,
                     type="double3")

        default_ext_setting = cmds.file(q=True, defaultExtensions=True)
        cmds.file(defaultExtensions=False)
        cmds.setAttr(uv_node+".wrapU", 0)
        original_path = cmds.getAttr(file_node+".fileTextureName")

        # Export to USD:
        file_suffix = "_{}_{}".format(convertTo, 'Metallic' if metallic else 'Specular')
        usd_path = os.path.abspath('UsdPreviewSurfaceRoundtripTest{}.usda'.format(file_suffix))

        export_options = [
            "shadingMode=useRegistry",
            "convertMaterialsTo=[{}]".format(convertTo),
            "mergeTransformAndShape=1"
        ]

        cmds.file(usd_path, force=True,
                  options=";".join(export_options),
                  typ="USD Export", pr=True, ea=True)

        cmds.file(defaultExtensions=default_ext_setting)

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

        # Import back:
        import_options = ("shadingMode=[[useRegistry,{}]]".format(convertTo),
                          "preferredMaterial=none",
                          "primPath=/")
        cmds.file(usd_path, i=True, type="USD Import",
                  ignoreVersion=True, ra=True, mergeNamespacesOnClash=False,
                  namespace="Test", pr=True, importTimeRange="combine",
                  options=";".join(import_options))

        # Check the new sphere is in the new shading group:
        self.assertTrue(cmds.sets(
            "pSphere1Shape",
            isMember=material_sg))

        # Check that we have no spurious "Looks" transform
        expectedTr = set(['front', 'persp', 'side', 'top', 'pSphere1'])
        allTr = set(cmds.ls(tr=True))
        self.assertEqual(allTr, expectedTr)

        # Check connections:
        self.assertEqual(
            cmds.connectionInfo(material_node+".outColor", dfs=True),
            [material_sg+".surfaceShader"])
        self.assertEqual(
            cmds.connectionInfo(material_node+".diffuseColor", sfd=True),
            file_node+".outColor")
        self.assertEqual(
            cmds.connectionInfo(file_node+".wrapU", sfd=True),
            "place2dTexture.wrapU")

        # Check values:
        self.assertAlmostEqual(cmds.getAttr(material_node+".ior"), 2)
        self.assertAlmostEqual(cmds.getAttr(material_node+".roughness"),
                               0.25)
        self.assertAlmostEqual(cmds.getAttr(material_node+".opacityThreshold"),
                               0.5)
        self.assertEqual(cmds.getAttr(material_node+".specularColor"),
                         [(0.125, 0.25, 0.75)])

        self.assertEqual(cmds.getAttr(material_node+".useSpecularWorkflow"), int(not metallic))

        self.assertEqual(cmds.getAttr(file_node+".defaultColor"),
                         [(0.5, 0.25, 0.125)])
        self.assertEqual(cmds.getAttr(file_node+".colorSpace"), "ACEScg")
        self.assertEqual(cmds.getAttr(file_node+".colorSpace"), "ACEScg")
        imported_path = cmds.getAttr(file_node+".fileTextureName")
        # imported path will be absolute:
        self.assertFalse(imported_path.startswith(".."))
        self.assertEqual(os.path.normpath(imported_path.lower()),
                         os.path.normpath(original_path.lower()))
        self.assertEqual(cmds.getAttr("place2dTexture.wrapU"), 0)
        self.assertEqual(cmds.getAttr("place2dTexture.wrapV"), 1)

        # Make sure paths are relative in the USD file. Joining the directory
        # that the USD file lives in with the texture path should point us at
        # a file that exists.
        file_template = "/{}/Looks/{}/{}"
        if convertTo == "MaterialX":
            file_template = "/{0}/Looks/{1}/MayaNG_{1}/{2}"
        stage = Usd.Stage.Open(usd_path)
        texture_prim = stage.GetPrimAtPath(
            file_template.format(sphere_xform, material_sg, file_node))
        rel_texture_path = texture_prim.GetAttribute('inputs:file').Get().path

        usd_dir = os.path.dirname(usd_path)
        full_texture_path = os.path.join(usd_dir, rel_texture_path)
        self.assertTrue(os.path.isfile(full_texture_path))

        self.assertTrue(mark.IsClean())

    def testShadingRoundtrip(self):
        """
        Test that shading group and surface node names will survive a roundtrip
        """
        mark = Tf.Error.Mark()
        mark.SetMark()
        self.assertTrue(mark.IsClean())

        mayaUsdPluginName = "mayaUsdPlugin"
        if not cmds.pluginInfo(mayaUsdPluginName, query=True, loaded=True):
            cmds.loadPlugin(mayaUsdPluginName)

        testPatterns = [
            # Each test has 5 elements:
            #   - Shader type
            #   - Initial shader name
            #   - Initial shading group name
            #   - Roundtrip shader name
            #   - Roundtrip shading group name

            # Fully modified names will survive a roundtrip:
            ("lambert", "bob", "bobSG", "bob", "bobSG"),
            # Modified sg name survive (and we do not touch surface name)
            ("blinn", "blinn42", "blueSG", "blinn42", "blueSG"),
            # Default surface names will survive even if mismatched:
            ("phong", "phong12", "blinn27SG", "phong12", "blinn27SG"),

            # WARNING: Meaninful surface names win over boring shading group
            # names, so this combination does not roundtrip. The final shading
            # group name will be modified to be consistent with the surface
            # shader name:
            ("blinn", "myGold", "blinn12SG",
                      "myGold", "myGoldSG"),
            ("usdPreviewSurface", "jersey12", "blinn27SG",
                                  "jersey12", "jersey12SG"),

            # This will make the UsdMaterial and UsdGeomSubset names more
            # meaningful.
        ]

        for sh_type, init_surf, init_sg, final_surf, final_sg in testPatterns:

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

            sphere_xform = cmds.polySphere()[0]

            material_node = cmds.shadingNode(sh_type, asShader=True,
                                             name=init_surf)
            material_sg = cmds.sets(renderable=True, noSurfaceShader=True,
                                    empty=True, name=init_sg)
            cmds.connectAttr(material_node+".outColor",
                             material_sg+".surfaceShader", force=True)
            cmds.sets(sphere_xform, e=True, forceElement=material_sg)

            default_ext_setting = cmds.file(q=True, defaultExtensions=True)
            cmds.file(defaultExtensions=False)

            # Export to USD:
            usd_path = os.path.abspath('%sRoundtripTest.usda' % init_surf)

            cmds.file(usd_path, force=True,
                    options="shadingMode=useRegistry;mergeTransformAndShape=1",
                    typ="USD Export", pr=True, ea=True)

            cmds.file(defaultExtensions=default_ext_setting)

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

            # Import back:
            import_options = ("shadingMode=[[useRegistry,UsdPreviewSurface]]",
                            "preferredMaterial=none",
                            "primPath=/")
            cmds.file(usd_path, i=True, type="USD Import",
                    ignoreVersion=True, ra=True, mergeNamespacesOnClash=False,
                    namespace="Test", pr=True, importTimeRange="combine",
                    options=";".join(import_options))

            # Check shading group name:
            self.assertTrue(cmds.sets("pSphere1Shape", isMember=final_sg))

            # Check surface name:
            self.assertEqual(
                cmds.connectionInfo(final_surf+".outColor", dfs=True),
                [final_sg+".surfaceShader"])

        self.assertTrue(mark.IsClean())

    def testDisplayColorLossyRoundtrip(self):
        """
        Test that shading group names created for display color import are in
        sync with their surface shaders.
        """
        mark = Tf.Error.Mark()
        mark.SetMark()
        self.assertTrue(mark.IsClean())

        mayaUsdPluginName = "mayaUsdPlugin"
        if not cmds.pluginInfo(mayaUsdPluginName, query=True, loaded=True):
            cmds.loadPlugin(mayaUsdPluginName)

        for i in range(1,4):
            sphere_xform = cmds.polySphere()[0]
            init_surf = "test%i" % i
            init_sg = init_surf + "SG"
            material_node = cmds.shadingNode("lambert", asShader=True,
                                             name=init_surf)
            material_sg = cmds.sets(renderable=True, noSurfaceShader=True,
                                    empty=True, name=init_sg)
            cmds.connectAttr(material_node+".outColor",
                             material_sg+".surfaceShader", force=True)
            cmds.sets(sphere_xform, e=True, forceElement=material_sg)

        # Export to USD:
        usd_path = os.path.abspath('DisplayColorRoundtripTest.usda')
        cmds.usdExport(mergeTransformAndShape=True,
            file=usd_path,
            shadingMode='none',
            exportDisplayColor=True)


        for preferred in ("blinn", "phong"):
            cmds.file(newFile=True, force=True)

            import_options = ("shadingMode=[[displayColor,default]]",
                              "preferredMaterial=%s" % preferred,
                              "primPath=/")
            cmds.file(usd_path, i=True, type="USD Import",
                    ignoreVersion=True, ra=True, mergeNamespacesOnClash=False,
                    namespace="Test", pr=True, importTimeRange="combine",
                    options=";".join(import_options))

            for i in ("", "1", "2"):
                # We expect blinn, blinn1, blinn2
                final_surf = "%s%s" % (preferred, i)
                # We expect blinnSG, blinn1SG, blinn2SG
                final_sg = final_surf + "SG"

                # Check surface name:
                self.assertEqual(
                    cmds.connectionInfo(final_surf+".outColor", dfs=True),
                    [final_sg+".surfaceShader"])

        self.assertTrue(mark.IsClean())

    def testUVReaderMerging(self):
        """
        Test that we produce a minimal number of UV readers
        """
        cmds.file(f=True, new=True)

        sphere_xform = cmds.polySphere()[0]

        material_node = cmds.shadingNode("usdPreviewSurface", asShader=True,
                                            name="ss01")
        material_sg = cmds.sets(renderable=True, noSurfaceShader=True,
                                empty=True, name="ss01SG")
        cmds.connectAttr(material_node+".outColor",
                            material_sg+".surfaceShader", force=True)
        cmds.sets(sphere_xform, e=True, forceElement=material_sg)

        # One file with UVs connected to diffuse:
        file_node = cmds.shadingNode("file", asTexture=True,
                                     isColorManaged=True)
        uv_node = cmds.shadingNode("place2dTexture", asUtility=True)
        cmds.setAttr(uv_node + ".offsetU", 0.125)
        cmds.setAttr(uv_node + ".offsetV", 0.5)
        connectUVNode(uv_node, file_node)
        cmds.connectAttr(file_node + ".outColor",
                         material_node + ".diffuseColor", f=True)

        # Another file, same UVs, connected to emissiveColor
        file_node = cmds.shadingNode("file", asTexture=True,
                                     isColorManaged=True)
        connectUVNode(uv_node, file_node)
        cmds.connectAttr(file_node + ".outColor",
                         material_node + ".emissiveColor", f=True)

        # Another file, no UVs, connected to metallic
        file_node = cmds.shadingNode("file", asTexture=True,
                                     isColorManaged=True)
        cmds.connectAttr(file_node + ".outColorR",
                         material_node + ".metallic", f=True)

        # Another file, no UVs, connected to roughness
        file_node = cmds.shadingNode("file", asTexture=True,
                                     isColorManaged=True)
        cmds.connectAttr(file_node + ".outColorR",
                         material_node + ".roughness", f=True)
        cmds.setAttr(file_node + ".offsetU", 0.25)
        cmds.setAttr(file_node + ".offsetV", 0.75)

        # Export to USD:
        usd_path = os.path.abspath('MinimalUVReader.usda')
        cmds.usdExport(mergeTransformAndShape=True,
            file=usd_path,
            shadingMode='useRegistry',
            exportDisplayColor=True)

        # We expect 2 primvar readers, and 2 st transforms:
        stage = Usd.Stage.Open(usd_path)
        mat_path = "/pSphere1/Looks/ss01SG/"

        # Here are the expected connections in the produced USD file:
        connections = [
            # Source node, input, destination node:
            ("ss01", "diffuseColor", "file1"),
            ("file1", "st", "place2dTexture1_UsdTransform2d"),
            ("place2dTexture1_UsdTransform2d", "in", "place2dTexture1"),

            ("ss01", "emissiveColor", "file2"),
            ("file2", "st", "place2dTexture1_UsdTransform2d"), # re-used
            # Note that the transform name is derived from place2DTexture name.

            ("ss01", "metallic", "file3"),
            ("file3", "st", "shared_TexCoordReader"), # no UV in Maya.

            ("ss01", "roughness", "file4"),
            ("file4", "st", "file4_UsdTransform2d"), # xform on file node
            ("file4_UsdTransform2d", "in", "shared_TexCoordReader")
            # Note that the transform name is derived from file node name.
        ]
        for src_name, input_name, dst_name in connections:
            src_prim = stage.GetPrimAtPath(mat_path + src_name)
            self.assertTrue(src_prim)
            src_shade = UsdShade.Shader(src_prim)
            self.assertTrue(src_shade)
            src_input = src_shade.GetInput(input_name)
            self.assertTrue(src_input.HasConnectedSource())
            (connect_api, out_name, _) = src_input.GetConnectedSource()
            self.assertEqual(connect_api.GetPath(), mat_path + dst_name)

    @unittest.skipUnless("mayaUtils" in globals() and mayaUtils.mayaMajorVersion() >= 2020, 'Requires standardSurface node which appeared in 2020.')
    def testOpacityRoundtrip(self):
        """
        Test that opacity roundtrips as expected.
        """
        filePath = os.path.join(self.inputPath,
                                "UsdExportImportRoundtripPreviewSurfaceTest",
                                "OpacityTests.ma")
        cmds.file(filePath, force=True, open=True)

        usd_path = os.path.abspath('OpacityRoundtripTest.usda')
        cmds.usdExport(mergeTransformAndShape=True,
                       file=usd_path,
                       shadingMode='useRegistry')

        stage = Usd.Stage.Open(usd_path)

        # We have 7 materials that are named:
        #    /pPlane6/Looks/standardSurface7SG/standardSurface7
        surf_base = "/pPlane{0}/Looks/standardSurface{1}SG/standardSurface{1}"
        # results for opacity are mostly connections to:
        #    /pPlane6/Looks/standardSurface7SG/file6.outputs:a
        cnx_base = "/pPlane{0}/Looks/standardSurface{1}SG/file{0}"
        # so we only need to expect a channel name:
        expected = ["r", "a", "r", "a", "g", "a", 0.4453652]

        for i, val in enumerate(expected):
            surf_path = surf_base.format(i+1, i+2)
            surf_prim = stage.GetPrimAtPath(surf_path)
            self.assertTrue(surf_prim)
            surf_shade = UsdShade.Shader(surf_prim)
            self.assertTrue(surf_shade)
            # useSpecularWorkflow is not exported anymore:
            use_specular_workflow = surf_shade.GetInput("useSpecularWorkflow")
            self.assertFalse(use_specular_workflow)
            opacity = surf_shade.GetInput("opacity")
            self.assertTrue(opacity)
            if (isinstance(val, float)):
                self.assertAlmostEqual(opacity.Get(), val)
            else:
                (connect_api, out_name, _) = opacity.GetConnectedSource()
                self.assertEqual(out_name, val)
                cnx_string = cnx_base.format(i+1, i+2)
                self.assertEqual(connect_api.GetPath(), cnx_string)

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

        # Re-import for a full roundtrip:
        # Import back:
        import_options = ("shadingMode=[[useRegistry,UsdPreviewSurface]]",
                          "preferredMaterial=standardSurface",
                          "primPath=/")
        cmds.file(usd_path, i=True, type="USD Import",
                  ignoreVersion=True, ra=True, mergeNamespacesOnClash=False,
                  namespace="Test", pr=True, importTimeRange="combine",
                  options=";".join(import_options))

        # The roundtrip is not perfect. We use explicit connections everywhere
        # on import:
        #
        # We expect the following connections:
        #      {'standardSurface2.opacityR': 'file1.outColorR',
        #       'standardSurface2.opacityG': 'file1.outColorR',
        #       'standardSurface2.opacityB': 'file1.outColorR'}
        #
        port_names = {"r": "outColorR", "g": "outColorG",
                      "b": "outColorB", "a": "outAlpha"}
        opacity_names = ["opacityR", "opacityG", "opacityB"]
        for i, val in enumerate(expected):
            if (isinstance(val, float)):
                for v in cmds.getAttr("standardSurface8.opacity")[0]:
                    self.assertAlmostEqual(v, val)
            else:
                cnx = cmds.listConnections("standardSurface{}".format(i+2),
                                           d=False, c=True, p=True)
                self.assertEqual(len(cnx), 6)
                for j in range(int(len(cnx)/2)):
                    k = cnx[2*j].split(".")
                    v = cnx[2*j+1].split(".")
                    self.assertEqual(len(k), 2)
                    self.assertEqual(len(v), 2)
                    self.assertTrue(k[1] in opacity_names)
                    self.assertEqual(v[0], "file{}".format(i+1))
                    self.assertEqual(v[1], port_names[val])

        cmds.file(f=True, new=True)
예제 #14
0
class ParentCmdTestCase(unittest.TestCase):
    '''Verify the Maya parent command on a USD scene.'''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()

    @classmethod
    def tearDownClass(cls):
        cmds.file(new=True, force=True)

        standalone.uninitialize()

    def setUp(self):
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Load a file that has the same scene in both the Maya Dag
        # hierarchy and the USD hierarchy.
        mayaUtils.openTestScene("parentCmd", "simpleSceneMayaPlusUSD_TRS.ma")

        # Clear selection to start off
        cmds.select(clear=True)

    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)

    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)

    @unittest.skipUnless(
        mayaUtils.previewReleaseVersion() >= 123,
        'Requires Maya fixes only available in Maya Preview Release 123 or later.'
    )
    def testParentAbsoluteSingleMatrixOp(self):
        """Test parent -absolute on prim with a single matrix op."""

        # Our test strategy is the following: we use the existing scene's cube
        # prim as reference, and set its local matrix onto a new prim that has
        # a single matrix op.  The cube prim is used as a reference.
        #
        # We then parent -absolute the new prim as well as the cube, and assert
        # that neither the new prim or the cube have moved in world space.

        # Create scene items for the cube, the cylinder, and the proxy shape.
        proxyShapePathStr = '|mayaUsdProxy1|mayaUsdProxyShape1'
        proxyShapePath = ufe.PathString.path(proxyShapePathStr)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        shapeSegment = mayaUtils.createUfePathSegment(proxyShapePathStr)
        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())

        # Create a capsule prim directly under the proxy shape
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])

        capsulePath = ufe.PathString.path(proxyShapePathStr + ',/Capsule1')

        capsulePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(capsulePath))
        capsuleXformable = UsdGeom.Xformable(capsulePrim)

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

        # Add a single matrix transform op to the capsule.
        capsulePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(capsulePath))
        capsuleXformable = UsdGeom.Xformable(capsulePrim)
        capsuleXformable.AddTransformOp()

        self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                         Vt.TokenArray(["xformOp:transform"]))

        # Delay creating the Transform3d interface until after we've added our
        # single matrix transform op, so that we get a UsdTransform3dMatrixOp.
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)
        capsuleT3d = ufe.Transform3d.transform3d(capsuleItem)

        # The cube is a direct child of the proxy shape, whose transform is the
        # identity, so the cube's world and local space transforms are identical
        cubeT3d = ufe.Transform3d.transform3d(cubeItem)
        cubeLocal = cubeT3d.matrix()
        cubeWorld = cubeT3d.inclusiveMatrix()
        cubeWorldListPre = matrixToList(cubeWorld)

        # Set the capsule's transform to be the same as the cube's.
        capsuleT3d.setMatrix(cubeLocal)

        self.assertEqual(capsuleXformable.GetXformOpOrderAttr().Get(),
                         Vt.TokenArray(["xformOp:transform"]))

        capsuleLocal = capsuleT3d.matrix()
        capsuleWorld = capsuleT3d.inclusiveMatrix()
        capsuleWorldListPre = matrixToList(capsuleWorld)

        assertVectorAlmostEqual(self, matrixToList(cubeLocal),
                                matrixToList(capsuleLocal))
        assertVectorAlmostEqual(self, matrixToList(cubeWorld),
                                matrixToList(capsuleWorld))

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

        # Confirm that the cube and capsule are now children of the cylinder.
        cylChildren = cylHier.children()
        self.assertEqual(len(cylChildren), 3)
        self.assertIn("cubeXform", childrenNames(cylChildren))
        self.assertIn("Capsule1", childrenNames(cylChildren))

        # Undo: cylinder no longer has children.
        cmds.undo()

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

        # Redo: confirm children are back.
        cmds.redo()

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

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

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

        # Undo everything.
        cmds.undo()

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

    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))

    def testIllegalChild(self):
        '''Parenting an object to a descendant must report an error.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            with self.assertRaises(RuntimeError):
                cmds.parent(
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1",
                    "|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1")

    def testAlreadyChild(self):
        '''Parenting an object to its current parent is a no-op.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            shapeSegment = mayaUtils.createUfePathSegment(
                "|mayaUsdProxy1|mayaUsdProxyShape1")
            spherePath = ufe.Path([
                shapeSegment,
                usdUtils.createUfePathSegment("/pCylinder1/pCube1/pSphere1")
            ])
            sphereItem = ufe.Hierarchy.createItem(spherePath)
            cylinderShapePath = ufe.Path([
                shapeSegment,
                usdUtils.createUfePathSegment("/pCylinder1/pCylinderShape1")
            ])
            cylinderShapeItem = ufe.Hierarchy.createItem(cylinderShapePath)
            parentPath = ufe.Path(
                [shapeSegment,
                 usdUtils.createUfePathSegment("/pCylinder1")])
            parentItem = ufe.Hierarchy.createItem(parentPath)

            parent = ufe.Hierarchy.hierarchy(parentItem)
            childrenPre = parent.children()

            # 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 cylinder
            self.assertNotIn("pSphere1", childrenNames(childrenPre))

            # The cylinder shape is a child of the cylinder
            self.assertIn("pCylinderShape1", childrenNames(childrenPre))

            # Parent the sphere and the cylinder shape to the cylinder.  This
            # is a no-op for the cylinder shape, as it's already a child of the
            # cylinder, and the sphere is parented to the cylinder.
            cmds.parent(ufe.PathString.string(spherePath),
                        ufe.PathString.string(cylinderShapePath),
                        ufe.PathString.string(parentPath))

            children = parent.children()
            self.assertEqual(len(childrenPre) + 1, len(children))
            self.assertIn("pSphere1", childrenNames(children))
            self.assertIn("pCylinderShape1", childrenNames(children))

            # Undo / redo
            cmds.undo()

            children = parent.children()
            self.assertEqual(len(childrenPre), len(children))
            self.assertNotIn("pSphere1", childrenNames(children))
            self.assertIn("pCylinderShape1", childrenNames(children))

            cmds.redo()

            children = parent.children()
            self.assertEqual(len(childrenPre) + 1, len(children))
            self.assertIn("pSphere1", childrenNames(children))
            self.assertIn("pCylinderShape1", childrenNames(children))

    def testUnparentUSD(self):
        '''Unparent USD node.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            # Unparent a USD node
            cubePathStr = '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1'
            cubePath = ufe.PathString.path(cubePathStr)
            cylinderItem = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1'))
            proxyShapeItem = ufe.Hierarchy.createItem(
                ufe.PathString.path('|mayaUsdProxy1|mayaUsdProxyShape1'))
            proxyShape = ufe.Hierarchy.hierarchy(proxyShapeItem)
            cylinder = ufe.Hierarchy.hierarchy(cylinderItem)

            def checkUnparent(done):
                proxyShapeChildren = proxyShape.children()
                cylinderChildren = cylinder.children()
                self.assertEqual('pCube1' in childrenNames(proxyShapeChildren),
                                 done)
                self.assertEqual('pCube1' in childrenNames(cylinderChildren),
                                 not done)

            checkUnparent(done=False)

            cmds.parent(cubePathStr, world=True)
            checkUnparent(done=True)

            cmds.undo()
            checkUnparent(done=False)

            cmds.redo()
            checkUnparent(done=True)

    def testUnparentMultiStage(self):
        '''Unparent USD nodes in more than one stage.'''

        with OpenFileCtx("simpleHierarchy.ma"):
            # An early version of this test imported the same file into the
            # opened file.  Layers are then shared between the stages, because
            # they come from the same USD file, causing changes done below one
            # proxy shape to be seen in the other.  Import from another file.
            filePath = testUtils.getTestScene("parentCmd",
                                              "simpleSceneUSD_TRS.ma")
            cmds.file(filePath, i=True)

            # Unparent a USD node in each stage.  Unparenting Lambert node is
            # nonsensical, but demonstrates the functionality.
            cubePathStr1 = '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1/pCube1'
            lambertPathStr2 = '|simpleSceneUSD_TRS_mayaUsdProxy1|simpleSceneUSD_TRS_mayaUsdProxyShape1,/initialShadingGroup/initialShadingGroup_lambert'

            cylinderItem1 = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|mayaUsdProxy1|mayaUsdProxyShape1,/pCylinder1'))
            shadingGroupItem2 = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|simpleSceneUSD_TRS_mayaUsdProxy1|simpleSceneUSD_TRS_mayaUsdProxyShape1,/initialShadingGroup'
                ))
            proxyShapeItem1 = ufe.Hierarchy.createItem(
                ufe.PathString.path('|mayaUsdProxy1|mayaUsdProxyShape1'))
            proxyShapeItem2 = ufe.Hierarchy.createItem(
                ufe.PathString.path(
                    '|simpleSceneUSD_TRS_mayaUsdProxy1|simpleSceneUSD_TRS_mayaUsdProxyShape1'
                ))
            cylinder1 = ufe.Hierarchy.hierarchy(cylinderItem1)
            shadingGroup2 = ufe.Hierarchy.hierarchy(shadingGroupItem2)
            proxyShape1 = ufe.Hierarchy.hierarchy(proxyShapeItem1)
            proxyShape2 = ufe.Hierarchy.hierarchy(proxyShapeItem2)

            def checkUnparent(done):
                proxyShape1Children = proxyShape1.children()
                proxyShape2Children = proxyShape2.children()
                cylinder1Children = cylinder1.children()
                shadingGroup2Children = shadingGroup2.children()
                self.assertEqual(
                    'pCube1' in childrenNames(proxyShape1Children), done)
                self.assertEqual('pCube1' in childrenNames(cylinder1Children),
                                 not done)
                self.assertEqual(
                    'initialShadingGroup_lambert'
                    in childrenNames(proxyShape2Children), done)
                self.assertEqual(
                    'initialShadingGroup_lambert'
                    in childrenNames(shadingGroup2Children), not done)

            checkUnparent(done=False)

            # Use relative parenting, else trying to keep absolute world
            # position of Lambert node fails (of course).
            cmds.parent(cubePathStr1, lambertPathStr2, w=True, r=True)
            checkUnparent(done=True)

            cmds.undo()
            checkUnparent(done=False)

            cmds.redo()
            checkUnparent(done=True)

    def testParentingToGPrim(self):
        '''Parenting an object to UsdGeomGprim object is not allowed'''

        # open tree scene
        mayaUtils.openTreeScene()

        with self.assertRaises(RuntimeError):
            cmds.parent(
                "|Tree_usd|Tree_usdShape,/TreeBase/trunk",
                "|Tree_usd|Tree_usdShape,/TreeBase/leavesXform/leaves")
예제 #15
0
    def testMayaHideAndShowHiddenUndoCommands(self):
        ''' Verify the token / attribute values for visibility via "hide", "showHidden" commands + Undo/Redo '''

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

        # create a Capsule and Cylinder via contextOps menu
        import mayaUsd_createStageWithNewLayer
        proxyShape = mayaUsd_createStageWithNewLayer.createStageWithNewLayer()
        proxyShapePath = ufe.PathString.path(proxyShape)
        proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath)
        proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem)
        proxyShapeContextOps.doOp(['Add New Prim', 'Capsule'])
        proxyShapeContextOps.doOp(['Add New Prim', 'Cylinder'])

        # capsule
        capsulePath = ufe.PathString.path('%s,/Capsule1' % proxyShape)
        capsuleItem = ufe.Hierarchy.createItem(capsulePath)
        capsulePrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(capsulePath))

        # cylinder
        cylinderPath = ufe.PathString.path('%s,/Cylinder1' % proxyShape)
        cylinderItem = ufe.Hierarchy.createItem(cylinderPath)
        cylinderPrim = mayaUsd.ufe.ufePathToPrim(
            ufe.PathString.string(cylinderPath))

        # stage / primSpec
        stage = mayaUsd.ufe.getStage(str(proxyShapePath))
        primSpecCapsule = stage.GetEditTarget().GetPrimSpecForScenePath(
            '/Capsule1')
        primSpecCylinder = stage.GetEditTarget().GetPrimSpecForScenePath(
            '/Cylinder1')

        # select capsule and cylinder prims
        ufe.GlobalSelection.get().append(capsuleItem)
        ufe.GlobalSelection.get().append(cylinderItem)

        # hide selected items
        cmds.hide(cs=True)

        # get the visibility "attribute"
        capsuleVisibleAttr = capsulePrim.GetAttribute('visibility')
        cylinderVisibleAttr = cylinderPrim.GetAttribute('visibility')

        # expect the visibility attribute to be 'invisible'
        self.assertEqual(capsuleVisibleAttr.Get(), 'invisible')
        self.assertEqual(cylinderVisibleAttr.Get(), 'invisible')

        # visibility "token" must exists now in the USD data model
        self.assertTrue(
            bool(primSpecCapsule
                 and UsdGeom.Tokens.visibility in primSpecCapsule.attributes))
        self.assertTrue(
            bool(primSpecCylinder
                 and UsdGeom.Tokens.visibility in primSpecCylinder.attributes))

        # undo
        cmds.undo()

        # expect the visibility attribute to be 'inherited'
        self.assertEqual(capsuleVisibleAttr.Get(), 'inherited')
        self.assertEqual(cylinderVisibleAttr.Get(), 'inherited')

        # visibility token must not exists now in the USD data model after undo
        self.assertFalse(
            bool(primSpecCapsule
                 and UsdGeom.Tokens.visibility in primSpecCapsule.attributes))
        self.assertFalse(
            bool(primSpecCylinder
                 and UsdGeom.Tokens.visibility in primSpecCylinder.attributes))

        # undo
        cmds.redo()

        # expect the visibility attribute to be 'invisible'
        self.assertEqual(capsuleVisibleAttr.Get(), 'invisible')
        self.assertEqual(cylinderVisibleAttr.Get(), 'invisible')

        # visibility "token" must exists now in the USD data model
        self.assertTrue(
            bool(primSpecCapsule
                 and UsdGeom.Tokens.visibility in primSpecCapsule.attributes))
        self.assertTrue(
            bool(primSpecCylinder
                 and UsdGeom.Tokens.visibility in primSpecCylinder.attributes))

        # hide selected items again
        cmds.hide(cs=True)

        # right after, call showHidden -all to make everything visible again
        cmds.showHidden(all=True)

        # expect the visibility attribute to be 'inherited'
        self.assertEqual(capsuleVisibleAttr.Get(), 'inherited')
        self.assertEqual(cylinderVisibleAttr.Get(), 'inherited')

        # This time, expect the visibility token to exists in the USD data model
        self.assertTrue(
            bool(primSpecCapsule
                 and UsdGeom.Tokens.visibility in primSpecCapsule.attributes))
        self.assertTrue(
            bool(primSpecCylinder
                 and UsdGeom.Tokens.visibility in primSpecCylinder.attributes))

        # undo the showHidden command
        cmds.undo()

        # expect the visibility attribute to be 'invisible'
        self.assertEqual(capsuleVisibleAttr.Get(), 'invisible')
        self.assertEqual(cylinderVisibleAttr.Get(), 'invisible')

        # visibility "token" must exists now in the USD data model
        self.assertTrue(
            bool(primSpecCapsule
                 and UsdGeom.Tokens.visibility in primSpecCapsule.attributes))
        self.assertTrue(
            bool(primSpecCylinder
                 and UsdGeom.Tokens.visibility in primSpecCylinder.attributes))

        # redo
        cmds.redo()

        # expect the visibility attribute to be 'inherited'
        self.assertEqual(capsuleVisibleAttr.Get(), 'inherited')
        self.assertEqual(cylinderVisibleAttr.Get(), 'inherited')

        # visibility "token" must exists now in the USD data model
        self.assertTrue(
            bool(primSpecCapsule
                 and UsdGeom.Tokens.visibility in primSpecCapsule.attributes))
        self.assertTrue(
            bool(primSpecCylinder
                 and UsdGeom.Tokens.visibility in primSpecCylinder.attributes))

        # MAYA-114407: Hide the stage, then reveal a stage item. The stage must unhide without crashing.
        if mayaUtils.previewReleaseVersion() >= 131:
            cmds.hide(proxyShape)
            self.assertEqual(cmds.getAttr("%s.visibility" % proxyShape), 0)
            cmds.showHidden("%s,/Cylinder1" % proxyShape, above=True)
            self.assertEqual(cmds.getAttr("%s.visibility" % proxyShape), 1)
예제 #16
0
class SelectByArrayTestCase(unittest.TestCase):
    '''Verify UFE selection on a USD scene by passing argument in arrays.'''

    pluginsLoaded = False

    @classmethod
    def setUpClass(cls):
        fixturesUtils.readOnlySetUpClass(__file__, loadPlugin=False)

        if not cls.pluginsLoaded:
            cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded()
    
    @classmethod
    def tearDownClass(cls):
        cmds.file(new=True, force=True)

        standalone.uninitialize()

    def setUp(self):
        # Load plugins
        self.assertTrue(self.pluginsLoaded)

        # Load a file that has the same scene in both the Maya Dag
        # hierarchy and the USD hierarchy.
        mayaUtils.openTestScene("parentCmd", "simpleSceneMayaPlusUSD_TRS.ma")

        shapeSegment = mayaUtils.createUfePathSegment(
            "|mayaUsdProxy1|mayaUsdProxyShape1")
        
        def makeUsdPath(name):
            return ufe.Path([shapeSegment, usdUtils.createUfePathSegment(name)])

        ufeNames = ["/cubeXform", "/cylinderXform", "/sphereXform"]
        self.usdPaths = [ufe.PathString.string(makeUsdPath(name)) for name in ufeNames]
        self.mayaPaths = ["pCube1", "pCylinder1", "pSphere1"]

        # Clear selection to start off
        cmds.select(clear=True)

    @unittest.skipUnless(mayaUtils.mayaMajorVersion() > 2022, 'testSelectMayaPathInMel only works starting with Maya 2022.')
    def testSelectMayaPathInMel(self):
        """
        Select multiple Maya items by passing them in an array to a mel command.
        """
        cmds.select(clear=True)
        melSelect(self.mayaPaths)

        sn = ufe.GlobalSelection.get()
        self.assertEqual(len(sn), 3)

    @unittest.skipUnless(mayaUtils.previewReleaseVersion() > 130, 'testSelectUFEInMel only works with fixes available in Maya 2023 after PR 130.')
    def testSelectUFEInMel(self):
        """
        Select multiple UFE items by passing them in an array to a mel command.
        """
        cmds.select(clear=True)
        melSelect(self.usdPaths)

        sn = ufe.GlobalSelection.get()
        self.assertEqual(len(sn), 3)

    @unittest.skipUnless(mayaUtils.previewReleaseVersion() > 130, 'testSelectUFEAndMayaInMel only works with fixes available in Maya 2023 after PR 130.')
    def testSelectUFEAndMayaInMel(self):
        """
        Select a mix of Maya and UFE items by passing them in an array to a mel command.
        """
        interleaved = []
        for ab in zip(self.mayaPaths, self.usdPaths):
            interleaved.append(ab[0])
            interleaved.append(ab[1])

        cmds.select(clear=True)
        melSelect(interleaved)

        sn = ufe.GlobalSelection.get()
        self.assertEqual(len(sn), 6)

    @unittest.skipUnless(mayaUtils.mayaMajorVersion() > 2022, 'testSelectMayaPathInPython only works starting with Maya 2022.')
    def testSelectMayaPathInPython(self):
        """
        Select multiple Maya items by passing them in an array to a Python command.
        """
        cmds.select(clear=True)
        cmds.select(self.mayaPaths)

        sn = ufe.GlobalSelection.get()
        self.assertEqual(len(sn), 3)

    @unittest.skipUnless(mayaUtils.previewReleaseVersion() > 130, 'testSelectUFEInPython only works with fixes available in Maya 2023 after PR 130.')
    def testSelectUFEInPython(self):
        """
        Select multiple UFE items by passing them in an array to a Python command.
        """
        cmds.select(clear=True)
        cmds.select(self.usdPaths)

        sn = ufe.GlobalSelection.get()
        self.assertEqual(len(sn), 3)

    @unittest.skipUnless(mayaUtils.previewReleaseVersion() > 130, 'testSelectUFEAndMayaInPython only works with fixes available in Maya 2023 after PR 130.')
    def testSelectUFEAndMayaInPython(self):
        """
        Select a mix of Maya and UFE items by passing them in an array to a Python command.
        """
        interleaved = []
        for ab in zip(self.mayaPaths, self.usdPaths):
            interleaved.append(ab[0])
            interleaved.append(ab[1])

        cmds.select(clear=True)
        cmds.select(interleaved)

        sn = ufe.GlobalSelection.get()
        self.assertEqual(len(sn), 6)