Esempio n. 1
0
    def testPerInstanceInheritedData(self):
        self._StartTest('perInstanceInheritedData')

        # These tests don't work in earlier versions of USD, the wrong
        # instance index gets selected
        if Usd.GetVersion() < (0, 20, 8):
            return

        # Hide and show some instances to make sure it updates correctly
        # These should start working correctly when MAYA-110508 is fixed
        stage = mayaUsdUfe.getStage("|stage|stageShape")
        ball_03_vis = stage.GetPrimAtPath('/root/group/ball_03').GetAttribute(
            'visibility')
        ball_04_vis = stage.GetPrimAtPath('/root/group/ball_04').GetAttribute(
            'visibility')

        cmds.select("|stage|stageShape,/root/group/ball_03")
        self.assertSnapshotClose('%s_ball_03_selected.png' % self._testName)

        ball_03_vis.Set('hidden')
        self.assertSnapshotClose('%s_ball_03_hidden.png' % self._testName)
        ball_04_vis.Set('hidden')
        self.assertSnapshotClose('%s_ball_03_and_04_hidden.png' %
                                 self._testName)
        ball_03_vis.Set('inherited')  # this should show the object again
        self.assertSnapshotClose('%s_ball_04_hidden.png' % self._testName)
        ball_04_vis.Set('inherited')
        self.assertSnapshotClose('%s_shown_after_hidden.png' % self._testName)

        # These tests behave differently before USD version 21.05, so don't run
        # them for those earlier versions.
        if Usd.GetVersion() < (0, 21, 5):
            return

        # Modify the purpose of some instances to make sure they update correctly
        # These should start working correctly when MAYA-110170 is fixed
        ball_03_purpose = stage.GetPrimAtPath(
            '/root/group/ball_03').GetAttribute('purpose')
        ball_04_purpose = stage.GetPrimAtPath(
            '/root/group/ball_04').GetAttribute('purpose')

        ball_03_purpose.Set('guide')
        self.assertSnapshotClose('%s_ball_03_guide.png' % self._testName)
        ball_04_purpose.Set('guide')
        self.assertSnapshotClose('%s_ball_03_and_04_guide.png' %
                                 self._testName)
        ball_03_purpose.Set('default')
        self.assertSnapshotClose('%s_ball_04_guide.png' % self._testName)
        ball_03_purpose.Set('default')
        self.assertSnapshotClose('%s_default_after_guide.png' % self._testName)
Esempio n. 2
0
    def setUpClass(cls):
        inputPath = fixturesUtils.readOnlySetUpClass(__file__)
        cmds.file(new=True, force=True)

        # We need to use different input usd files depending on the version of USD.
        # We do this because USD introduced breaking changes on lights in USD 21.*
        # More precisely :
        #
        # - Before USD 21.* : lights attributes didn't have any prefix. Attributes
        #                     were named "intensity", "color", etc...
        # - In 21.02        : The attributes from light schemas were added a
        #                     namespace "inputs". But attributes from UsdLuxShapingAPI
        #                     (used for spot lights) or UsdLuxShadowsAPI (for shadows)
        #                     were still left without this namespace
        # - In 21.05        : Attributes from UsdLuxShadows and UsdLuxShapingAPI also
        #                     have a namespace "inputs"
        testFile = "LightsTest.usda"
        usdVersion = Usd.GetVersion()
        if usdVersion < (0, 21, 2):
            testFile = "LightsTest_2011.usda"
        elif usdVersion == (0, 21, 2):
            testFile = "LightsTest_2102.usda"

        # Import from USD.
        usdFilePath = os.path.join(inputPath, "UsdImportLightTest", testFile)
        cmds.usdImport(file=usdFilePath, readAnimData=True)
        cls._stage = Usd.Stage.Open(usdFilePath)
Esempio n. 3
0
    def test_SkelImport(self):
        cmds.file(new=True, force=True)

        path = os.path.join(self.inputPath, "UsdImportSkeleton",
                            "skelCube.usda")

        cmds.usdImport(file=path,
                       readAnimData=True,
                       primPath="/Root",
                       shadingMode=[
                           ["none", "default"],
                       ])

        stage = Usd.Stage.Open(path)
        skelCache = UsdSkel.Cache()

        bindingSitePrim = stage.GetPrimAtPath("/Root")
        self.assertTrue(bindingSitePrim.IsA(UsdSkel.Root))

        if Usd.GetVersion() > (0, 20, 8):
            skelCache.Populate(UsdSkel.Root(bindingSitePrim),
                               Usd.PrimDefaultPredicate)
        else:
            skelCache.Populate(UsdSkel.Root(bindingSitePrim))

        skel = UsdSkel.Skeleton.Get(stage, "/Root/Skeleton")
        self.assertTrue(skel)

        skelQuery = skelCache.GetSkelQuery(skel)
        self.assertTrue(skelQuery)

        meshPrim = stage.GetPrimAtPath("/Root/Cube")
        self.assertTrue(meshPrim)

        skinningQuery = skelCache.GetSkinningQuery(meshPrim)
        self.assertTrue(skinningQuery)

        jointNames = [
            name.split("/")[-1] for name in skelQuery.GetJointOrder()
        ]

        joints = [_GetDepNode(n) for n in jointNames]
        self.assertTrue(all(joints))

        self._ValidateJointTransforms(skelQuery, joints)
        self._ValidateJointBindPoses(skelQuery, joints)

        self._ValidateBindPose("Skeleton_bindPose", skelQuery, joints)

        self._ValidateMeshTransform(meshPrim.GetName(), skinningQuery)

        self._ValidateSkinClusterRig(joints=joints,
                                     skinClusterName="skinCluster_{}".format(
                                         meshPrim.GetName()),
                                     groupPartsName="skinClusterGroupParts",
                                     groupIdName="skinClusterGroupId",
                                     bindPoseName="Skeleton_bindPose",
                                     meshName=meshPrim.GetName(),
                                     usdSkelQuery=skelQuery,
                                     usdSkinningQuery=skinningQuery)
    def testExportPxrRisShading(self):
        """
        Tests that exporting a Maya mesh with a simple Maya shading setup
        results in the correct shading on the USD mesh.
        """
        cubePrim = self._stage.GetPrimAtPath('/MarbleCube/Geom/Cube')
        self.assertTrue(cubePrim)

        # Validate the Material prim bound to the Mesh prim.
        materialBindingAPI = UsdShade.MaterialBindingAPI(cubePrim)
        material = materialBindingAPI.ComputeBoundMaterial()[0]
        self.assertTrue(material)
        materialPath = material.GetPath().pathString
        self.assertEqual(materialPath, '/MarbleCube/Materials/MarbleCubeSG')

        if Usd.GetVersion() >= (0, 21, 5):
            # For USD 21.05 and later, GetInputs() and GetOutputs() take an
            # "onlyAuthored" argument that is True by default, so in that case
            # we expect only one output on the material for the "surface"
            # terminal in the "ri" renderContext that the export should have
            # authored.
            expectedNumOutputs = 1
        else:
            # Otherwise prior to USD 21.05, GetInputs() and GetOutputs() did
            # not take any arguments and always included the built-in
            # terminals for the universal renderContext as well as any other
            # authored terminals.
            expectedNumOutputs = 4

        # Validate the surface shader that is connected to the material.
        materialOutputs = material.GetOutputs()
        self.assertEqual(len(materialOutputs), expectedNumOutputs)
        print(self._stage.ExportToString())
        materialOutput = material.GetOutput('ri:surface')
        (connectableAPI, outputName, outputType) = materialOutput.GetConnectedSource()
        self.assertEqual(outputName, 'out')
        shader = UsdShade.Shader(connectableAPI)
        self.assertTrue(shader)

        shaderId = shader.GetIdAttr().Get()
        self.assertEqual(shaderId, 'PxrMayaMarble')

        # Validate the connected input on the surface shader.
        shaderInput = shader.GetInput('placementMatrix')
        self.assertTrue(shaderInput)

        (connectableAPI, outputName, outputType) = shaderInput.GetConnectedSource()
        self.assertEqual(outputName, 'worldInverseMatrix')
        shader = UsdShade.Shader(connectableAPI)
        self.assertTrue(shader)

        shaderId = shader.GetIdAttr().Get()
        self.assertEqual(shaderId, 'PxrMayaPlacement3d')
Esempio n. 5
0
    def testPointInstancerGrid7k(self):
        mayaUtils.openPointInstancesGrid7kScene()
        cmds.FrameAllInAllViews()
        # Set the USD point instances pick mode to "PointInstancer" so that we pick
        # the instancer and not point instances instances during the test.
        cmds.optionVar(stringValue=(
            testUtilsSelectabilityPointInstanceSelection._pointInstancesPickModeOptionVarName, 'PointInstancer'))

        # In USD versions before 21.05, the point instancer pick mode did not exists.
        # For those version we end-up selecting the prototypes, of which there are 7.
        expectedCount = 1 if Usd.GetVersion() >= (0, 21, 2) else 7
        
        self._RunTest(expectedCount)
    def testNodeTypes(self):
        '''Engine method to run scene item test.'''

        # Get a UFE scene item for one of the balls in the scene.
        ball35Path = ufe.Path([
            mayaUtils.createUfePathSegment("|transform1|proxyShape1"),
            usdUtils.createUfePathSegment("/Room_set/Props/Ball_35")
        ])
        ball35Item = ufe.Hierarchy.createItem(ball35Path)

        # Ball35 is an Xform.
        ball35NodeType = ball35Item.nodeType()
        self.assertEqual(ball35NodeType, "Xform")

        # This node type should be the first item on the ancestor node type list
        # and it should have other items.
        ball35AncestorNodeTypes = ball35Item.ancestorNodeTypes()
        if Usd.GetVersion() > (0, 20, 2):
            self.assertEqual(ball35NodeType, ball35AncestorNodeTypes[0])
        else:
            self.assertEqual("UsdGeomXform", ball35AncestorNodeTypes[0])

        self.assertTrue(len(ball35AncestorNodeTypes) > 1)
    def testMayaReferenceAttributes(self):
        layer = Sdf.Layer.CreateAnonymous()
        stageOut = Usd.Stage.Open(layer.identifier)

        primPath = Sdf.AssetPath('/MayaReference')
        mayaReferencePath = '/somewherenice/path.ma'
        mayaNamespace = 'nsp'

        primOut = stageOut.DefinePrim(primPath.path, 'MayaReference')
        self.assertTrue(primOut.IsValid())

        mayaReferenceOut = mayaUsdSchemas.MayaReference(primOut)
        self.assertTrue(mayaReferenceOut.GetPrim())
        if Usd.GetVersion() > (0, 20, 2):
            typeName = Usd.SchemaRegistry().GetSchemaTypeName(
                mayaReferenceOut._GetStaticTfType())
        else:
            typeName = mayaReferenceOut.GetSchemaClassPrimDefinition().typeName
        self.assertEqual(typeName, 'MayaReference')

        mayaReferenceAttr = primOut.CreateAttribute('mayaReference',
                                                    Sdf.ValueTypeNames.Asset)
        mayaReferenceAttr.Set(mayaReferencePath)

        mayaNamespaceAttr = primOut.CreateAttribute('mayaNamespace',
                                                    Sdf.ValueTypeNames.String)
        mayaNamespaceAttr.Set(mayaNamespace)

        stageIn = Usd.Stage.Open(stageOut.Flatten())
        primIn = stageIn.GetPrimAtPath(primPath.path)
        self.assertTrue(primIn.IsValid())

        mayaReferenceIn = mayaUsdSchemas.MayaReference(primIn)
        self.assertTrue(mayaReferenceIn.GetMayaReferenceAttr().Get(),
                        mayaReferencePath)
        self.assertTrue(mayaReferenceIn.GetMayaNamespaceAttr().Get(),
                        mayaNamespace)
Esempio n. 8
0
    def testSkelTransforms(self):
        """
        Tests that the computed joint transforms in USD, when tarnsformed into
        world space, match the world space transforms of the Maya joints.
        """

        mayaFile = os.path.join(self.inputPath, "UsdExportSkeletonTest",
                                "UsdExportSkeleton.ma")
        cmds.file(mayaFile, force=True, open=True)

        # frameRange = [1, 30]
        frameRange = [1, 3]

        # TODO: The joint hierarchy intentionally includes non-joint nodes,
        # which are expected to be ignored. However, when we try to extract
        # restTransforms from the dagPose, the intermediate transforms cause
        # problems, since they are not members of the dagPose. As a result,
        # no dag pose is exported. Need to come up with a way to handle this
        # correctly in export.
        print("Expect warnings about invalid restTransforms")
        usdFile = os.path.abspath('UsdExportSkeleton.usda')
        cmds.usdExport(mergeTransformAndShape=True,
                       file=usdFile,
                       shadingMode='none',
                       frameRange=frameRange,
                       exportSkels='auto')
        stage = Usd.Stage.Open(usdFile)

        root = UsdSkel.Root.Get(stage, '/SkelChar')
        self.assertTrue(root)

        skelCache = UsdSkel.Cache()

        if Usd.GetVersion() > (0, 20, 8):
            skelCache.Populate(root, Usd.PrimDefaultPredicate)
        else:
            skelCache.Populate(root)

        skel = UsdSkel.Skeleton.Get(stage, '/SkelChar/Hips')
        self.assertTrue(skel)

        skelQuery = skelCache.GetSkelQuery(skel)
        self.assertTrue(skelQuery)

        xfCache = UsdGeom.XformCache()

        for frame in range(*frameRange):
            cmds.currentTime(frame, edit=True)
            xfCache.SetTime(frame)

            skelLocalToWorld = xfCache.GetLocalToWorldTransform(
                skelQuery.GetPrim())

            usdJointXforms = skelQuery.ComputeJointSkelTransforms(frame)

            for joint, usdJointXf in zip(skelQuery.GetJointOrder(),
                                         usdJointXforms):

                usdJointWorldXf = usdJointXf * skelLocalToWorld

                selList = OM.MSelectionList()
                selList.add(Sdf.Path(joint).name)

                dagPath = selList.getDagPath(0)
                mayaJointWorldXf = Gf.Matrix4d(*dagPath.inclusiveMatrix())

                self.assertTrue(
                    Gf.IsClose(mayaJointWorldXf, usdJointWorldXf, 1e-5))
Esempio n. 9
0
class testSchemaApiAdaptor(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._rootPath = fixturesUtils.setUpClass(__file__)

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

    def setUp(self):
        pass

    @unittest.skipUnless(Usd.GetVersion() > (0, 21, 2),
                         'USD Lux becomes connectable in 21.05.')
    def testMinimalAdaptation(self):
        """Test that we can adapt ShadowAPI to an existing light shape. This exercises the most
        basic callbacks exposed by a SchemaAPI adaptor:"""

        mayaUsdLib.SchemaApiAdaptor.Register(shadowApiAdaptorShape, "light",
                                             "ShadowAPI")

        lightShape1 = cmds.pointLight()
        cmds.setAttr(lightShape1 + ".shadowColor", 0.5, 0.25, 0)
        lightShape2 = cmds.pointLight()
        cubeXform, cubeShape = cmds.polyCube()

        # Wrong type: not adapted
        adaptor = mayaUsdLib.Adaptor(cubeShape)
        self.assertEqual(adaptor.GetAppliedSchemas(), [])

        # Wrong name: not adapted
        adaptor = mayaUsdLib.Adaptor(lightShape2)
        self.assertEqual(adaptor.GetAppliedSchemas(), [])

        # Adapted:
        adaptor = mayaUsdLib.Adaptor(lightShape1)
        self.assertEqual(adaptor.GetAppliedSchemas(), ["ShadowAPI"])

        schema = adaptor.GetSchemaByName("ShadowAPI")
        self.assertTrue(schema)

        self.assertEqual(set(schema.GetAuthoredAttributeNames()),
                         set(["inputs:shadow:color", "inputs:shadow:enable"]))
        colorAttr = schema.GetAttribute("inputs:shadow:color")
        self.assertTrue(colorAttr)
        linearizedValue = (0.21763764, 0.047366142, 0)
        colorAttrValue = colorAttr.Get()
        self.assertAlmostEqual(linearizedValue[0], colorAttrValue[0])
        self.assertAlmostEqual(linearizedValue[1], colorAttrValue[1])
        self.assertEqual(linearizedValue[2], colorAttrValue[2])

        colorAttr.Set((1, 0, 1))
        self.assertEqual(cmds.getAttr(lightShape1 + ".shadowColor"),
                         [(1.0, 0.0, 1.0)])

    @unittest.skipUnless(HAS_BULLET, 'Requires the bullet plugin.')
    def testComplexAdaptation(self):
        """Test that we can adapt a bullet simulation"""

        mayaUsdLib.SchemaApiAdaptor.Register(TestBulletMassShemaAdaptor,
                                             "shape", "PhysicsMassAPI")
        mayaUsdLib.SchemaApiAdaptor.Register(TestBulletRigidBodyShemaAdaptor,
                                             "shape", "PhysicsRigidBodyAPI")

        # Build a scene (and exercise the adaptor in a freeform context)
        cmds.file(f=True, new=True)

        s1T = cmds.polySphere()[0]
        cmds.loadPlugin("bullet")
        if not BulletUtils.checkPluginLoaded():
            return

        rbT, rbShape = RigidBody.CreateRigidBody().command(
            autoFit=True,
            colliderShapeType=RigidBody.eShapeType.kColliderSphere,
            meshes=[s1T],
            radius=1.0,
            mass=5.0,
            centerOfMass=(0.9, 0.8, 0.7))

        # See if the plugin adaptor can read the bullet shape under the mesh:
        sl = om.MSelectionList()
        sl.add(s1T)
        dagPath = sl.getDagPath(0)
        dagPath.extendToShape()

        adaptor = mayaUsdLib.Adaptor(dagPath.fullPathName())
        self.assertEqual(adaptor.GetUsdType(),
                         Tf.Type.FindByName('UsdGeomMesh'))
        # NOTICE: PhysicsRigidBodyAPI is not in the list because that API is
        # supported only on export!!!
        self.assertEqual(adaptor.GetAppliedSchemas(), ['PhysicsMassAPI'])
        physicsMass = adaptor.GetSchemaByName("PhysicsMassAPI")
        self.assertEqual(physicsMass.GetName(), "PhysicsMassAPI")
        massAttributes = set([
            'physics:centerOfMass', 'physics:density',
            'physics:diagonalInertia', 'physics:mass', 'physics:principalAxes'
        ])
        self.assertEqual(set(physicsMass.GetAttributeNames()), massAttributes)
        bulletAttributes = set(['physics:centerOfMass', 'physics:mass'])
        self.assertEqual(set(physicsMass.GetAuthoredAttributeNames()),
                         bulletAttributes)
        bulletMass = physicsMass.GetAttribute('physics:mass')
        self.assertAlmostEqual(bulletMass.Get(), 5.0)
        bulletMass.Set(12.0)

        bulletCenter = physicsMass.GetAttribute('physics:centerOfMass')
        bulletCenter.Set(Gf.Vec3f(3, 4, 5))

        sl = om.MSelectionList()
        sl.add(s1T)
        bulletPath = sl.getDagPath(0)
        bulletPath.extendToShape(1)
        massDepFn = om.MFnDependencyNode(bulletPath.node())
        plug = om.MPlug(bulletPath.node(), massDepFn.attribute("mass"))
        self.assertAlmostEqual(plug.asFloat(), 12.0)

        # Create an untranslated attribute:
        usdDensity = physicsMass.CreateAttribute('physics:density')
        usdDensity.Set(33.0)
        self.assertAlmostEqual(usdDensity.Get(), 33.0)

        # This will result in a dynamic attribute on the bulletShape:
        plug = massDepFn.findPlug("USD_ATTR_physics_density", True)
        self.assertAlmostEqual(plug.asFloat(), 33.0)
        bulletAttributes.add('physics:density')
        self.assertEqual(set(physicsMass.GetAuthoredAttributeNames()),
                         bulletAttributes)

        physicsMass.RemoveAttribute('physics:density')
        bulletAttributes.remove('physics:density')
        self.assertEqual(set(physicsMass.GetAuthoredAttributeNames()),
                         bulletAttributes)

        # Add some animation:
        cmds.setKeyframe(bulletPath, at="mass", t=0, v=3.0)
        cmds.setKeyframe(bulletPath, at="mass", t=10, v=30.0)

        # Modify the velocity so it can be exported to the RBD Physics schema.
        cmds.setKeyframe(bulletPath, at="initialVelocityX", t=0, v=5.0)
        cmds.setKeyframe(bulletPath, at="initialVelocityX", t=10, v=50.0)
        cmds.setKeyframe(bulletPath, at="initialVelocityY", t=0, v=6.0)
        cmds.setKeyframe(bulletPath, at="initialVelocityY", t=10, v=60.0)
        cmds.setKeyframe(bulletPath, at="initialVelocityZ", t=0, v=7.0)
        cmds.setKeyframe(bulletPath, at="initialVelocityZ", t=10, v=70.0)

        # Try applying the schema on a new sphere:
        s2T = cmds.polySphere()[0]
        sl.add(s2T)
        dagPath = sl.getDagPath(1)
        dagPath.extendToShape()
        adaptor = UsdMaya.Adaptor(dagPath.fullPathName())
        physicsMass = adaptor.ApplySchemaByName("PhysicsMassAPI")
        self.assertEqual(physicsMass.GetName(), "PhysicsMassAPI")
        self.assertEqual(adaptor.GetUsdType(),
                         Tf.Type.FindByName('UsdGeomMesh'))
        self.assertEqual(adaptor.GetAppliedSchemas(), ['PhysicsMassAPI'])

        usdDensity = physicsMass.CreateAttribute('physics:density')
        usdDensity.Set(33.0)

        # Export, but without enabling Bullet:
        usdFilePath = os.path.abspath('UsdExportSchemaApiTest_NoBullet.usda')
        cmds.mayaUSDExport(mergeTransformAndShape=True, file=usdFilePath)

        # Check that there are no Physics API schemas exported:
        stage = Usd.Stage.Open(usdFilePath)
        for i in (1, 2):
            spherePrim = stage.GetPrimAtPath(
                '/pSphere{0}/pSphereShape{0}'.format(i))
            self.assertFalse(
                "PhysicsMassAPI" in spherePrim.GetAppliedSchemas())

        schemasToExport = ["PhysicsMassAPI", "PhysicsRigidBodyAPI"]
        # Export, with Bullet:
        usdFilePath = os.path.abspath('UsdExportSchemaApiTest_WithBullet.usda')
        cmds.mayaUSDExport(mergeTransformAndShape=True,
                           file=usdFilePath,
                           apiSchema=schemasToExport,
                           frameRange=(1, 10))

        # Check that Physics API schemas did get exported:
        stage = Usd.Stage.Open(usdFilePath)
        values = [
            ("physics:centerOfMass", (Gf.Vec3f(3, 4, 5), Gf.Vec3f(0, 0, 0))),
            ("physics:mass", (3.0, 1.0)),
            ("physics:density", (None, 33.0)),
        ]
        for i in (1, 2):
            spherePrim = stage.GetPrimAtPath(
                '/pSphere{0}/pSphereShape{0}'.format(i))
            self.assertTrue("PhysicsMassAPI" in spherePrim.GetAppliedSchemas())
            for n, v in values:
                if v[i - 1]:
                    a = spherePrim.GetAttribute(n)
                    self.assertEqual(a.Get(), v[i - 1])
            if i == 1:
                # Is mass animated?
                a = spherePrim.GetAttribute("physics:mass")
                self.assertEqual(a.Get(10), 30)
            # This got exported even though invisible in interactive:
            self.assertTrue(
                "PhysicsRigidBodyAPI" in spherePrim.GetAppliedSchemas())
            a = spherePrim.GetAttribute("physics:velocity")
            if i == 1:
                self.assertEqual(a.Get(0), (5, 6, 7))
                self.assertEqual(a.Get(10), (50, 60, 70))
                numberOfExportedKeys = len(a.GetTimeSamples())

        # Try unapplying the schema:
        adaptor.UnapplySchemaByName("PhysicsMassAPI")
        self.assertEqual(adaptor.GetAppliedSchemas(), [])

        # Test import of USDPhysics without job context:
        cmds.file(new=True, force=True)
        cmds.mayaUSDImport(f=usdFilePath)

        sl = om.MSelectionList()
        # pSphereShape1 is a transform, since the bullet shape prevented merging the mesh and the
        # transform. The shape will be pSphereShape1Shape...
        sl.add("pSphereShape1")
        bulletPath = sl.getDagPath(0)
        # No bullet shape since we did not put Bullet as jobContext
        self.assertEqual(bulletPath.numberOfShapesDirectlyBelow(), 1)

        cmds.file(new=True, force=True)
        cmds.mayaUSDImport(f=usdFilePath,
                           apiSchema=schemasToExport,
                           readAnimData=True)

        sl = om.MSelectionList()
        sl.add("pSphereShape1")
        bulletPath = sl.getDagPath(0)
        # Finds bullet shape since we did put Bullet as jobContext

        self.assertEqual(bulletPath.numberOfShapesDirectlyBelow(), 2)

        # The bullet shape has animated mass and initial velocity since we read the animation.
        bulletPath.extendToShape(1)
        massDepFn = om.MFnDependencyNode(bulletPath.node())
        for attrName in ("mass", "initialVelocityX", "initialVelocityY",
                         "initialVelocityZ"):
            plug = om.MPlug(bulletPath.node(), massDepFn.attribute(attrName))
            self.assertTrue(plug.isConnected)
            fcurve = oma.MFnAnimCurve(plug.source().node())
            self.assertEqual(fcurve.numKeys, numberOfExportedKeys)
Esempio n. 10
0
    def _ValidateUsdLuxLight(self, lightTypeName):
        primPathFormat = '/RfMLightsTest/Lights/%s'

        lightPrimPath = primPathFormat % lightTypeName
        lightPrim = self._stage.GetPrimAtPath(lightPrimPath)
        self.assertTrue(lightPrim)

        testNumber = None
        if lightTypeName == 'CylinderLight':
            self.assertTrue(lightPrim.IsA(UsdLux.CylinderLight))
            testNumber = 1
        elif lightTypeName == 'DiskLight':
            self.assertTrue(lightPrim.IsA(UsdLux.DiskLight))
            testNumber = 2
        elif lightTypeName == 'DistantLight':
            self.assertTrue(lightPrim.IsA(UsdLux.DistantLight))
            testNumber = 3
        elif lightTypeName == 'DomeLight':
            self.assertTrue(lightPrim.IsA(UsdLux.DomeLight))
            testNumber = 4
        elif lightTypeName == 'MeshLight':
            self.assertTrue(lightPrim.IsA(UsdLux.GeometryLight))
            testNumber = 5
        elif lightTypeName == 'RectLight':
            self.assertTrue(lightPrim.IsA(UsdLux.RectLight))
            testNumber = 6
        elif lightTypeName == 'SphereLight':
            self.assertTrue(lightPrim.IsA(UsdLux.SphereLight))
            testNumber = 7
        elif lightTypeName == 'AovLight':
            self.assertTrue(lightPrim.GetTypeName(), "PxrAovLight")
            testNumber = 8
        elif lightTypeName == 'EnvDayLight':
            self.assertTrue(lightPrim.GetTypeName(), "PxrEnvDayLight")
            testNumber = 9
        else:
            raise NotImplementedError('Invalid light type %s' % lightTypeName)

        usdVersion = Usd.GetVersion()
        if usdVersion < (0, 21, 11):
            lightSchema = UsdLux.Light(lightPrim)
        else:
            lightSchema = UsdLux.LightAPI(lightPrim)
        self.assertTrue(lightSchema)

        if lightTypeName == 'AovLight':
            # PxrAovLight doesn't have any of the below attributes.
            return

        expectedIntensity = 1.0 + (testNumber * 0.1)
        self.assertTrue(
            Gf.IsClose(lightSchema.GetIntensityAttr().Get(), expectedIntensity,
                       1e-6))

        expectedExposure = 0.1 * testNumber
        self.assertTrue(
            Gf.IsClose(lightSchema.GetExposureAttr().Get(), expectedExposure,
                       1e-6))

        expectedDiffuse = 1.0 + (testNumber * 0.1)
        self.assertTrue(
            Gf.IsClose(lightSchema.GetDiffuseAttr().Get(), expectedDiffuse,
                       1e-6))

        expectedSpecular = 1.0 + (testNumber * 0.1)
        self.assertTrue(
            Gf.IsClose(lightSchema.GetSpecularAttr().Get(), expectedSpecular,
                       1e-6))

        if lightTypeName == 'EnvDayLight':
            # PxrEnvDayLight doesn't have any of the below attributes.
            return

        if lightTypeName == 'DomeLight':
            # PxrDomeLight has no normalize attribute
            self.assertFalse(lightSchema.GetNormalizeAttr().HasAuthoredValue())
        else:
            expectedNormalize = True
            self.assertEqual(lightSchema.GetNormalizeAttr().Get(),
                             expectedNormalize)

        expectedColor = Gf.Vec3f(0.1 * testNumber)
        self.assertTrue(
            Gf.IsClose(lightSchema.GetColorAttr().Get(), expectedColor, 1e-6))

        expectedEnableTemperature = True
        self.assertEqual(lightSchema.GetEnableColorTemperatureAttr().Get(),
                         expectedEnableTemperature)

        expectedTemperature = 6500.0 + testNumber
        self.assertTrue(
            Gf.IsClose(lightSchema.GetColorTemperatureAttr().Get(),
                       expectedTemperature, 1e-6))
Esempio n. 11
0
    def createAppliedSchemasSection(self):
        # USD version 0.21.2 is required because of
        # Usd.SchemaRegistry().GetPropertyNamespacePrefix()
        if Usd.GetVersion() < (0, 21, 2):
            return

        showAppliedSchemasSection = False

        # loop on all applied schemas and store all those
        # schema into a dictionary with the attributes.
        # Storing the schema into a dictionary allow us to
        # group all instances of a MultipleApply schema together
        # so we can later display them into the same UI section.
        #
        # By example, if UsdCollectionAPI is applied twice, UsdPrim.GetAppliedSchemas()
        # will return ["CollectionAPI:instance1","CollectionAPI:instance2"] but we want to group
        # both instance inside a "CollectionAPI" section.
        #
        schemaAttrsDict = {}
        appliedSchemas = self.prim.GetAppliedSchemas()
        for schema in appliedSchemas:
            if Usd.GetVersion() > (0, 21, 5):
                typeAndInstance = Usd.SchemaRegistry().GetTypeNameAndInstance(
                    schema)
            else:
                typeAndInstance = Usd.SchemaRegistry().GetTypeAndInstance(
                    schema)
            typeName = typeAndInstance[0]
            schemaType = Usd.SchemaRegistry().GetTypeFromName(typeName)

            if schemaType.pythonClass:
                isMultipleApplyAPISchema = Usd.SchemaRegistry(
                ).IsMultipleApplyAPISchema(typeName)
                if isMultipleApplyAPISchema:
                    # get the attributes names. They will not include the namespace and instance name.
                    instanceName = typeAndInstance[1]
                    attrList = schemaType.pythonClass.GetSchemaAttributeNames(
                        False, instanceName)
                    # build the real attr name
                    # By example, collection:lightLink:includeRoot
                    namespace = Usd.SchemaRegistry(
                    ).GetPropertyNamespacePrefix(typeName)
                    prefix = namespace + ":" + instanceName + ":"
                    attrList = [prefix + i for i in attrList]

                    if typeName in schemaAttrsDict:
                        schemaAttrsDict[typeName] += attrList
                    else:
                        schemaAttrsDict[typeName] = attrList
                else:
                    attrList = schemaType.pythonClass.GetSchemaAttributeNames(
                        False)
                    schemaAttrsDict[typeName] = attrList

                # The "Applied Schemas" will be only visible if at least
                # one applied Schemas has attribute.
                if not showAppliedSchemasSection:
                    for attr in attrList:
                        if self.attrS.hasAttribute(attr):
                            showAppliedSchemasSection = True
                            break

        # Create the "Applied Schemas" section
        # with all the applied schemas
        if showAppliedSchemasSection:
            with ufeAeTemplate.Layout(self, 'Applied Schemas', collapse=True):
                for typeName, attrs in schemaAttrsDict.items():
                    typeName = self.sectionNameFromSchema(typeName)
                    self.createSection(typeName, attrs, False)
Esempio n. 12
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)
    def testExportAsClip(self):
        """
        Test that a maya scene exports to usd the same way if it is exported
        all at once, or in 5 frame clips and then stitched back together.
        """
        # generate clip files and validate num samples on points attribute
        clipFiles = []
        # first 5 frames have no animation
        usdFile = os.path.abspath('UsdExportAsClip_cube.001.usda')
        clipFiles.append(usdFile)
        cmds.usdExport(mergeTransformAndShape=True,
                       file=usdFile,
                       frameRange=(1, 5),
                       sss=False)
        stage = Usd.Stage.Open(usdFile)
        self._ValidateNumSamples(stage, '/world/pCube1', 'points', 1)

        # next 5 frames have no animation
        usdFile = os.path.abspath('UsdExportAsClip_cube.005.usda')
        clipFiles.append(usdFile)
        cmds.usdExport(mergeTransformAndShape=True,
                       file=usdFile,
                       frameRange=(5, 10),
                       sss=False)
        stage = Usd.Stage.Open(usdFile)
        self._ValidateNumSamples(stage, '/world/pCube1', 'points', 1)

        # next 5 frames have deformation animation
        usdFile = os.path.abspath('UsdExportAsClip_cube.010.usda')
        clipFiles.append(usdFile)
        frames = (10, 15)
        cmds.usdExport(mergeTransformAndShape=True,
                       file=usdFile,
                       frameRange=frames,
                       sss=False)
        stage = Usd.Stage.Open(usdFile)
        self._ValidateNumSamples(stage, '/world/pCube1', 'points',
                                 frames[1] + 1 - frames[0])

        # next 5 frames have no animation
        usdFile = os.path.abspath('UsdExportAsClip_cube.015.usda')
        clipFiles.append(usdFile)
        cmds.usdExport(mergeTransformAndShape=True,
                       file=usdFile,
                       frameRange=(15, 20),
                       sss=False)
        stage = Usd.Stage.Open(usdFile)
        self._ValidateNumSamples(stage, '/world/pCube1', 'points', 1)

        stitchedPath = os.path.abspath('result.usda')
        stitchedLayer = Sdf.Layer.CreateNew(stitchedPath)

        # Clip stitching behavior changed significantly between core USD 20.05
        # and 20.08. Beginning with 20.08, we need to pass an additional option
        # to ensure that authored time samples are held across gaps in value
        # clips.
        if Usd.GetVersion() > (0, 20, 5):
            self.assertTrue(
                UsdUtils.StitchClips(stitchedLayer,
                                     clipFiles,
                                     '/world',
                                     startFrame=1,
                                     endFrame=20,
                                     interpolateMissingClipValues=True,
                                     clipSet='default'))
        else:
            self.assertTrue(
                UsdUtils.StitchClips(stitchedLayer,
                                     clipFiles,
                                     '/world',
                                     startFrame=1,
                                     endFrame=20,
                                     clipSet='default'))

        # export a non clip version for comparison
        canonicalUsdFile = os.path.abspath('canonical.usda')
        cmds.usdExport(mergeTransformAndShape=True,
                       file=canonicalUsdFile,
                       frameRange=(1, 20),
                       sss=False)

        print('comparing: \nnormal: {}\nstitched: {}'.format(
            canonicalUsdFile, stitchedPath))
        canonicalStage = Usd.Stage.Open(canonicalUsdFile)
        clipsStage = Usd.Stage.Open(stitchedPath)
        # visible
        self._ValidateSamples(canonicalStage, clipsStage, '/world/pCube1',
                              'visibility', (0, 21))
        # animated visibility
        self._ValidateSamples(canonicalStage, clipsStage, '/world/pCube2',
                              'visibility', (0, 21))
        # hidden, non animated:
        self._ValidateSamples(canonicalStage, clipsStage, '/world/pCube4',
                              'visibility', (0, 21))
        # constant points:
        self._ValidateSamples(canonicalStage, clipsStage, '/world/pCube2',
                              'points', (0, 21))
        # blend shape driven animated points:
        self._ValidateSamples(canonicalStage, clipsStage, '/world/pCube3',
                              'points', (0, 21))
        # animated points:
        self._ValidateSamples(canonicalStage, clipsStage, '/world/pCube1',
                              'points', (0, 21))