def testReexport(self): cmds.mayaUSDImport(file=self._usdFile, primPath='/') exportedUsdFile = os.path.abspath('ExoticTypeNames.reexported.usda') cmds.mayaUSDExport(file=exportedUsdFile) stage = Usd.Stage.Open(exportedUsdFile) self.assertTrue(stage) self.assertTrue(stage.GetPrimAtPath('/A')) self.assertEqual(stage.GetPrimAtPath('/A').GetTypeName(), 'Xform') self.assertTrue(stage.GetPrimAtPath('/A/A_1')) self.assertEqual(stage.GetPrimAtPath('/A/A_1').GetTypeName(), '') self.assertTrue(stage.GetPrimAtPath('/A/A_1/A_1_I')) self.assertEqual(stage.GetPrimAtPath('/A/A_1/A_1_I').GetTypeName(), '') self.assertTrue(stage.GetPrimAtPath('/A/A_1/A_1_II')) self.assertEqual(stage.GetPrimAtPath('/A/A_1/A_1_II').GetTypeName(), '') # Originally Cube, but not on re-export! self.assertTrue(stage.GetPrimAtPath('/A/A_1/A_1_III')) self.assertEqual(stage.GetPrimAtPath('/A/A_1/A_1_III').GetTypeName(), 'Scope') self.assertTrue(stage.GetPrimAtPath('/A/A_2')) self.assertEqual(stage.GetPrimAtPath('/A/A_2').GetTypeName(), 'Scope') self.assertTrue(stage.GetPrimAtPath('/B')) self.assertEqual(stage.GetPrimAtPath('/B').GetTypeName(), '') self.assertTrue(stage.GetPrimAtPath('/B/B_1')) self.assertEqual(stage.GetPrimAtPath('/B/B_1').GetTypeName(), 'Xform')
def testImportSimpleUnoptimized(self): """ Test importing the following non-optimized USD simple instancing scenario: pCube1 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube3 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) MayaExportedInstanceSources pCube1_pCubeShape1 [Scope] pCubeShape1 [Mesh] The goal would be for Maya to skip all [Scope] items and end up with: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube3 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) But that will happen in a second phase. We only make sure this imports without coding errors here. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "SimpleScenario_unoptimized.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # pCube1 is not an instance: ('pCube1',), # Bad: We have an extra transform added here for the USD scope and # no sharing of first pCubeShape1 transform. ('pCube1|pCubeShape1',), ('pCube2|pCubeShape1',), ('pCube3|pCubeShape1',), # Correctly shared via instancing, but with 2 extra transforms: ('pCube1|pCubeShape1|pCubeShape1', 'pCube2|pCubeShape1|pCubeShape1', 'pCube3|pCubeShape1|pCubeShape1'), ('pCube1|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube2|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube3|pCubeShape1|pCubeShape1|pCubeShape1Shape'), ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
def setUpClass(cls): inputPath = fixturesUtils.readOnlySetUpClass(__file__) usdFilePath = os.path.join(inputPath, 'UsdImportRfMShadersTest', 'MarbleCube.usda') cls._stage = Usd.Stage.Open(usdFilePath) cmds.file(new=True, force=True) cmds.mayaUSDImport(file=usdFilePath, shadingMode=[['useRegistry', 'rendermanForMaya'], ])
def test_framerate_imports(self): """Test that the USD's timesamples are appropriately converted to match the Mayas uiUnits""" # Only check a few of these since the logic should # hold up the same no matter what, but fps_map = {"ntsc": 30, "film": 24, "ntscf": 60} base_rate = self.stage.GetTimeCodesPerSecond() start = self.stage.GetStartTimeCode() end = self.stage.GetEndTimeCode() nodes = { 'Root|joint1': self.getKeyframes("/Root/Skin"), 'Root|Mesh': self.getKeyframes("/Root/Mesh"), "blendShape1": self.getKeyframes("/Root/Skin"), # Mesh Anim "Cam": self.getKeyframes("/Root/Cam"), # For some reason the clip planes don't identify keys on the Cam Shape directly "CamShape_nearClipPlane": self.getKeyframes("/Root/Cam"), "blendShape2": self.getKeyframes("/Root/Nurbs") # Patch anim } # TODO: Add NurbsCurves to the nodes list when the importer supports it for name, fps in fps_map.items(): cmds.file(new=True, force=True) cmds.currentUnit(time=name) cmds.playbackOptions(minTime=10) cmds.playbackOptions(maxTime=20) time_scale = (fps / base_rate) cmds.mayaUSDImport(f=self.usd_file, readAnimData=True) # First make sure the playback slider is set correctly self.assertAlmostEquals( start * time_scale, cmds.playbackOptions(query=True, minTime=True)) self.assertAlmostEquals( end * time_scale, cmds.playbackOptions(query=True, maxTime=True)) for node, frames in nodes.items(): mframes = sorted(set(cmds.keyframe(node, query=True))) numMFrames = len(mframes) numFrames = len(frames) self.assertTrue( numFrames == numMFrames, "A mismatched number of frames was found {}:{}".format( numFrames, numMFrames)) for idx, frame in enumerate(frames): self.assertAlmostEquals(frame * time_scale, mframes[idx])
def testNoDisplayColors(self): """ Do not get default down to display colors: """ modes = [["useRegistry", "maya"], ["useRegistry", "UsdPreviewSurface"]] cmds.mayaUSDImport(file=self.usd_path, shadingMode=modes, preferredMaterial="none", primPath="/") expected = [["pCube1Shape", None], ["pCube2Shape", "standardSurface"], ["pCube3Shape", "usdPreviewSurface"]] self.checkMaterials(expected)
def testNoImport(self): """ Do not import any materials. """ modes = [ ["none", "default"], ] cmds.mayaUSDImport(file=self.usd_path, shadingMode=modes, primPath="/") expected = [["pCube1Shape", None], ["pCube2Shape", None], ["pCube3Shape", None]] self.checkMaterials(expected)
def testImportSimpleOptimized(self): """ Test importing the following optimized USD simple instancing scenario: pCube1 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) pCube2 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) pCube3 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) MayaExportedInstanceSources pCube1_instanceParent [Xform] pCubeShape1 [Mesh] The goal would be for Maya to end up with this: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube3 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) This would require finding out that pCube1_instanceParent was not merged with pCubeShape1 and do not require adding an extra unmerging transform when reading the Gprim. But that will happen in a second phase. We only make sure this imports without coding errors here. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "SimpleScenario_optimized.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # pCube1 is not an instance: ('pCube1',), # Correctly shared via instancing, but with 1 extra transforms: ('pCube1|pCubeShape1', 'pCube2|pCubeShape1', 'pCube3|pCubeShape1'), ('pCube1|pCubeShape1|pCubeShape1Shape', 'pCube2|pCubeShape1|pCubeShape1Shape', 'pCube3|pCubeShape1|pCubeShape1Shape'), ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
def testUsdImport(self): """ This test executes import from an anonymous layer as per GitHub Discussion https://github.com/Autodesk/maya-usd/discussions/1069 """ layer = Sdf.Layer.CreateAnonymous() stage = Usd.Stage.Open(layer) stage.DefinePrim('/Foo', 'Xform') cmds.mayaUSDImport(f=layer.identifier, primPath='/') expectedMayaNodesSet = set(['|Foo']) mayaNodesSet = set(cmds.ls('|Foo*', long=True)) self.assertEqual(expectedMayaNodesSet, mayaNodesSet)
def testImportExportInstancesTest(self): """ Test importing the USD currently generated by testExportInstances. The goal would be for Maya to end up with the same node hierarchy as the original scene: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] (instance "source") pCubeShape2 [mesh] (instance "source") pCube3 [transform] (instance "source") pCubeShape2 (instance pCube1|pCube2|pCubeShape2) pCube4 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) There is some work to do to achieve that. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "UsdExportInstancesTest_original.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # Correctly shared via instancing, but with 2 extra transforms: ('pCube1|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube4|pCubeShape1|pCubeShape1|pCubeShape1Shape'), ('pCube1|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube1|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape'), # Incorrect: Would expect all pCube2 to be shared: ('pCube1|pCube2',), ('pCube4|pCube2',), # Incorrect: Would expect all pCube3 to be shared: ('pCube1|pCube3',), ('pCube4|pCube3',), ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
def testUSDZImport(self): mark = Tf.Error.Mark() mark.SetMark() self.assertTrue(mark.IsClean()) om.MFileIO.newFile(True) write_dir_path = self.temp_dir cmds.mayaUSDImport(f=self.usdz_path, importUSDZTextures=True, importUSDZTexturesFilePath=write_dir_path) self.assertTrue( os.path.isfile(os.path.join(write_dir_path, "clouds_128_128.png"))) self.assertTrue( os.path.isfile(os.path.join(write_dir_path, "red_128_128.png"))) self.assertTrue(mark.IsClean())
def testImportCommand(self): """ Tests a custom exporter for a conversion that exists in an unloaded plugin using the mayaUSDImport command. """ modes = [["useRegistry", "maya"], ["useRegistry", "UsdPreviewSurface"], ["displayColor", "default"]] cmds.mayaUSDImport(file=self.usd_path, shadingMode=modes, preferredMaterial="none", primPath="/") expected = [["pCube1Shape", "lambert"], ["pCube2Shape", "standardSurface"], ["pCube3Shape", "usdPreviewSurface"]] self.checkMaterials(expected)
def testImport(self): cmds.mayaUSDImport(file=self._usdFile, primPath='/') dagObjects = cmds.ls(long=True, dag=True) self.assertIn('|A', dagObjects) self.assertEqual(cmds.nodeType('|A'), 'transform') self.assertFalse(cmds.getAttr('|A.tx', lock=True)) self.assertIn('|A|A_1', dagObjects) self.assertEqual(cmds.nodeType('|A|A_1'), 'transform') self.assertEqual(cmds.getAttr('|A|A_1.USD_typeName'), '') self.assertTrue(cmds.getAttr('|A|A_1.tx', lock=True)) self.assertIn('|A|A_1|A_1_I', dagObjects) self.assertEqual(cmds.nodeType('|A|A_1|A_1_I'), 'transform') self.assertEqual(cmds.getAttr('|A|A_1|A_1_I.USD_typeName'), '') self.assertTrue(cmds.getAttr('|A|A_1|A_1_I.tx', lock=True)) # This one is special: unknown types get a Maya "note" on import for # aid debugging import problems. (Note that we don't have a Cube # importer.) self.assertIn('|A|A_1|A_1_II', dagObjects) self.assertEqual(cmds.nodeType('|A|A_1|A_1_II'), 'transform') self.assertEqual(cmds.getAttr('|A|A_1|A_1_II.USD_typeName'), '') self.assertIn('Cube', cmds.getAttr('|A|A_1|A_1_II.notes')) self.assertTrue(cmds.getAttr('|A|A_1|A_1_II.tx', lock=True)) self.assertIn('|A|A_1|A_1_III', dagObjects) self.assertEqual(cmds.nodeType('|A|A_1|A_1_III'), 'transform') self.assertEqual(cmds.getAttr('|A|A_1|A_1_III.USD_typeName'), 'Scope') self.assertTrue(cmds.getAttr('|A|A_1|A_1_III.tx', lock=True)) self.assertIn('|A|A_2', dagObjects) self.assertEqual(cmds.nodeType('|A|A_2'), 'transform') self.assertEqual(cmds.getAttr('|A|A_2.USD_typeName'), 'Scope') self.assertTrue(cmds.getAttr('|A|A_2.tx', lock=True)) self.assertIn('|B', dagObjects) self.assertEqual(cmds.nodeType('|B'), 'transform') self.assertEqual(cmds.getAttr('|B.USD_typeName'), '') self.assertTrue(cmds.getAttr('|B.tx', lock=True)) self.assertIn('|B|B_1', dagObjects) self.assertEqual(cmds.nodeType('|B|B_1'), 'transform') self.assertFalse(cmds.getAttr('|B|B_1.tx', lock=True))
def testUSDZImportIntoProject(self): mark = Tf.Error.Mark() mark.SetMark() self.assertTrue(mark.IsClean()) project_path = os.path.normpath(cmds.workspace(q=True, rd=True)) image_path = os.path.join(project_path, cmds.workspace(fre="sourceImages")) # Purposefully erase the sourceimage directory: if os.path.isdir(image_path): shutil.rmtree(image_path) # It will automatically get recreated: om.MFileIO.newFile(True) cmds.mayaUSDImport(f=self.usdz_path, importUSDZTextures=True) self.assertTrue( os.path.isfile(os.path.join(image_path, "clouds_128_128.png"))) self.assertTrue( os.path.isfile(os.path.join(image_path, "red_128_128.png"))) self.assertTrue(mark.IsClean())
def testSimpleImportChaser(self): mayaUsdLib.ImportChaser.Register(importChaserTest, "info") rootPaths = cmds.mayaUSDImport(v=True, f=self.stagePath, chaser=['info']) self.assertEqual(len(rootPaths), 1) sl = OpenMaya.MSelectionList() sl.add(rootPaths[0]) root = sl.getDependNode(0) fnNode = OpenMaya.MFnDependencyNode(root) self.assertTrue(fnNode.hasAttribute("customData")) plgCustomData = fnNode.findPlug("customData", True) customDataStr = plgCustomData.asString() self.assertEqual(customDataStr, "Custom layer data: customKeyAcustomValueA\ncustomKeyBcustomValueB\n")
def testImportChaser(self): cmds.loadPlugin('usdTestInfoImportChaser') # NOTE: (yliangsiew) We load this plugin since the chaser is compiled as part of it. rootPaths = cmds.mayaUSDImport(v=True, f=self.stagePath, chaser=['info']) self.assertEqual(len(rootPaths), 1) sl = om.MSelectionList() sl.add(rootPaths[0]) root = om.MObject() sl.getDependNode(0, root) fnNode = om.MFnDependencyNode(root) self.assertTrue(fnNode.hasAttribute("customData")) plgCustomData = fnNode.findPlug("customData") customDataStr = plgCustomData.asString() self.assertEqual(customDataStr, "Custom layer data: customKeyAcustomValueA\ncustomKeyBcustomValueB\n")
def testMeshNextToProxyShapeAndImported(self): """ Tests the colors between a mesh with (0.55, 0.55, 0.55) exporting that file and then re-importing it, and also referencing it back into the same scene through a proxy shape. While this is a bit more than just "GL" testing, it's a useful place to centralize all this. If we don't like that this is testing usdImport functionality, we can remove This will render as follows: blank | usdImport | ---------+----------- modeled | ref'd | """ x = self._PlaneWithColor((0.55, 0.55, 0.55)) cmds.select(x) usdFile = os.path.join(self._testDir, 'plane.usd') cmds.mayaUSDExport(file=usdFile, selection=True, shadingMode='none', exportDisplayColor=True) proxyShape = cmds.createNode('mayaUsdProxyShape', name='usdProxyShape') proxyTransform = cmds.listRelatives(proxyShape, parent=True, fullPath=True)[0] cmds.xform(proxyTransform, translation=(30.48, 0, 0)) cmds.setAttr('%s.filePath' % proxyShape, usdFile, type='string') cmds.setAttr('%s.primPath' % proxyShape, '/pPlane1', type='string') x = cmds.mayaUSDImport(file=usdFile) cmds.xform(x, translation=(30.48, 30.48, 0)) cmds.setAttr("hardwareRenderingGlobals.floatingPointRTEnable", 0) cmds.setAttr('defaultColorMgtGlobals.outputTransformEnabled', 0) self._Snapshot('default') cmds.setAttr("hardwareRenderingGlobals.floatingPointRTEnable", 1) cmds.setAttr('defaultColorMgtGlobals.outputTransformEnabled', 1) self._Snapshot('colorMgt')
def testComponentTags(self): """ Test that component tags can round-trip through USD. """ geo = cmds.polySphere(sx=6, sy=6)[0] geo_name = geo geo = geo_utils.extendToShape(geo) ctag_utils.createTag(geo, 'top', ['f[18:23]', 'f[30:35]']) ctag_utils.createTag(geo, 'bottom', ['f[0:5]', 'f[24:29]']) usdFilePath = os.path.join(os.environ.get('MAYA_APP_DIR'), 'testComponentTags.usda') cmds.mayaUSDExport(mergeTransformAndShape=True, file=usdFilePath, shadingMode='none') cmds.file(new=True, force=True) rootPaths = cmds.mayaUSDImport(v=True, f=usdFilePath) self.assertEqual(len(rootPaths), 1) indices = cmds.getAttr('{0}.componentTags'.format(geo_name), multiIndices=True) or [] self.assertEqual(len(indices), 2)
def testUSDZImport(self): om.MFileIO.newFile(True) write_dir_path = os.path.dirname(self.usdz_path) cmds.mayaUSDImport(f=self.usdz_path, importUSDZTextures=True, importUSDZTexturesFilePath=write_dir_path) self.assertTrue(os.path.isfile(os.path.join(write_dir_path, "clouds_128_128.png"))) self.assertTrue(os.path.isfile(os.path.join(write_dir_path, "red_128_128.png")))
def testImportComplexOptimizedTest(self): """ Testing a complex scenario, where one of the instances has been modified: Maya scene: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] (instance "source") pCubeShape2 [mesh] (instance "source") pCube3 [transform] (instance "source") pCubeShape2 (instance pCube1|pCube2|pCubeShape2) pCube4 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) pCube5 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) someUniqueChildOf_pCube5 [transform] We would expect the following unoptimized USD export to reload correctly: pCube1 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) pCube4 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) pCube5 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) someUniqueChildOf_pCube5 [Xform] MayaExportedInstanceSources pCube1_pCubeShape1 [Scope] pCubeShape1 [Mesh] pCube1_pCube2 [Scope] pCube2 [Xform] (instance /MayaExportedInstanceSources/pCube1_pCube2_instancedParent) pCube1_pCube3 [Scope] pCube3 [Xform] (instance /MayaExportedInstanceSources/pCube1_pCube2_instancedParent) pCube1_instanceParent [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) pCube1_pCube2_instancedParent [Xform] pCubeShape2 [Mesh] Here we have created a simplified instance for children of pCube1 and pCube4, but kept unoptimized masters for pCube5. The instance pCube1_instanceParent is sharing the unoptimized instances. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "ComplexScenario_optimized.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # Correctly shared via instancing, but with 2 extra transforms: ('pCube1|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube4|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube5|pCubeShape1|pCubeShape1|pCubeShape1Shape'), # The modified pCube5 does not share at the pCubeShape1 level: ('pCube1|pCubeShape1', 'pCube4|pCubeShape1'), ('pCube5|pCubeShape1',), # Correctly shared, with 2 extra transform: ('pCube1|pCube2|pCube2|pCubeShape2|pCubeShape2Shape', 'pCube1|pCube3|pCube3|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube2|pCube2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube3|pCube3|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube2|pCube2|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube3|pCube3|pCubeShape2|pCubeShape2Shape'), # All pCube2|pCube2 and pCube3|pCube3 are shared: ('pCube1|pCube2|pCube2', 'pCube4|pCube2|pCube2', 'pCube5|pCube2|pCube2',), ('pCube1|pCube3|pCube3', 'pCube4|pCube3|pCube3', 'pCube5|pCube3|pCube3',), # But at the pCube2 and pCube3 level the modified pCube5 is unshared: ('pCube1|pCube2', 'pCube4|pCube2'), ('pCube5|pCube2',), ('pCube1|pCube3', 'pCube4|pCube3'), ('pCube5|pCube3',), ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
def testImportComplexUnoptimizedTest(self): """ Testing a complex scenario, where one of the instances has been modified: Maya scene: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] (instance "source") pCubeShape2 [mesh] (instance "source") pCube3 [transform] (instance "source") pCubeShape2 (instance pCube1|pCube2|pCubeShape2) pCube4 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) pCube5 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) someUniqueChildOf_pCube5 [transform] We would expect the following unoptimized USD export to reload correctly: pCube1 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) pCube4 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) pCube5 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) someUniqueChildOf_pCube5 [Mesh] MayaExportedInstanceSources pCube1_pCubeShape1 [Scope] pCubeShape1 [Mesh] pCube1_pCube2 [Scope] pCube2 [Xform] pCubeShape2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2_pCubeShape2) pCube1_pCube3 [Scope] pCube3 [Xform] pCubeShape2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2_pCubeShape2) pCube1_pCube2_pCubeShape2 [Scope] pCubeShape2 [Mesh] There is some work to do to achieve that. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "ComplexScenario_unoptimized.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # Correctly shared via instancing, but with 2 extra transforms: ('pCube1|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube4|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube5|pCubeShape1|pCubeShape1|pCubeShape1Shape'), # Correctly shared, but with 3 extra transforms: ('pCube1|pCube2|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube1|pCube3|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube2|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube3|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube2|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube3|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape'), # Incorrect: Would expect all pCube2 to be shared: ('pCube1|pCube2',), ('pCube4|pCube2',), ('pCube5|pCube2',), # Incorrect: Would expect all pCube3 to be shared: ('pCube1|pCube3',), ('pCube4|pCube3',), ('pCube5|pCube3',), # But at the pCube2|pCube2 level, we get correct sharing: ('pCube1|pCube2|pCube2', 'pCube4|pCube2|pCube2', 'pCube5|pCube2|pCube2'), # Same with pCube3|pCube3: ('pCube1|pCube3|pCube3', 'pCube4|pCube3|pCube3', 'pCube5|pCube3|pCube3'), ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
def testImportExportReworkOptimizedTest(self): """ Testing another way testExportInstances could export to USD that would be easier to re-read. We would export optimized to: pCube1 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) pCube4 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) pCube5 [Xform] (instance /MayaExportedInstanceSources/pCube1_instanceParent) MayaExportedInstanceSources pCube1_instanceParent [Xform] pCubeShape1 [Mesh] pCube2 [Xform] (instance /MayaExportedInstanceSources/pCube1_pCube2_instancedParent) pCube3 [Xform] (instance /MayaExportedInstanceSources/pCube1_pCube2_instancedParent) pCube1_pCube2_instancedParent [Xform] pCubeShape2 [Mesh] The optimization is detecting that all children of pCube1 are instances allowing a complex master to be created. The goal would be for Maya to end up with the same node hierarchy as the original scene: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] (instance "source") pCubeShape2 [mesh] (instance "source") pCube3 [transform] (instance "source") pCubeShape2 (instance pCube1|pCube2|pCubeShape2) pCube4 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) pCube5 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) There is some work to do to achieve that. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "UsdExportInstancesTest_optimized.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # Correctly shared via instancing, but with 1 extra transforms: ('pCube1|pCubeShape1|pCubeShape1Shape', 'pCube4|pCubeShape1|pCubeShape1Shape', 'pCube5|pCubeShape1|pCubeShape1Shape'), # Correctly shared, with 1 extra transform: ('pCube1|pCube2|pCubeShape2|pCubeShape2Shape', 'pCube1|pCube3|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube3|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube2|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube3|pCubeShape2|pCubeShape2Shape'), # All pCube2 are shared: ('pCube1|pCube2', 'pCube4|pCube2', 'pCube5|pCube2',), # All pCube3 are shared: ('pCube1|pCube3', 'pCube4|pCube3', 'pCube5|pCube3',) ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
def testImportExportReworkUnoptimizedTest(self): """ Testing another way testExportInstances could export to USD that would be easier to re-read. We would export unoptimized to: pCube1 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) pCube4 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) pCube5 [Xform] pCubeShape1 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCubeShape1) pCube2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2) pCube3 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube3) MayaExportedInstanceSources pCube1_pCubeShape1 [Scope] pCubeShape1 [Mesh] pCube1_pCube2 [Scope] pCube2 [Xform] pCubeShape2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2_pCubeShape2) pCube1_pCube3 [Scope] pCube3 [Xform] pCubeShape2 [Scope] (instance /MayaExportedInstanceSources/pCube1_pCube2_pCubeShape2) pCube1_pCube2_pCubeShape2 [Scope] pCubeShape2 [Mesh] The goal would be for Maya to end up with the same node hierarchy as the original scene by skipping all [Scope] primitives: pCube1 [transform] pCubeShape1 [mesh] (instance "source") pCube2 [transform] (instance "source") pCubeShape2 [mesh] (instance "source") pCube3 [transform] (instance "source") pCubeShape2 (instance pCube1|pCube2|pCubeShape2) pCube4 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) pCube5 [transform] pCubeShape1 [mesh] (instance pCube1|pCubeShape1) pCube2 [transform] (instance pCube1|pCube2) pCube3 [transform] (instance pCube1|pCube3) There is some work to do to achieve that. """ usd_file = os.path.join(self._path, "UsdImportInstancesTest", "UsdExportInstancesTest_unoptimized.usda") cmds.mayaUSDImport(file=usd_file, primPath="/") # Format is sorted all paths of an instance clique: expected_cliques = [ # Correctly shared via instancing, but with 2 extra transforms: ('pCube1|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube4|pCubeShape1|pCubeShape1|pCubeShape1Shape', 'pCube5|pCubeShape1|pCubeShape1|pCubeShape1Shape'), # Correctly shared, but with 3 extra transforms: ('pCube1|pCube2|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube1|pCube3|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube2|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube4|pCube3|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube2|pCube2|pCubeShape2|pCubeShape2|pCubeShape2Shape', 'pCube5|pCube3|pCube3|pCubeShape2|pCubeShape2|pCubeShape2Shape'), # Incorrect: Would expect all pCube2 to be shared: ('pCube1|pCube2',), ('pCube4|pCube2',), ('pCube5|pCube2',), # Incorrect: Would expect all pCube3 to be shared: ('pCube1|pCube3',), ('pCube4|pCube3',), ('pCube5|pCube3',), # But at the pCube2|pCube2 level, we get correct sharing: ('pCube1|pCube2|pCube2', 'pCube4|pCube2|pCube2', 'pCube5|pCube2|pCube2'), # Same with pCube3|pCube3: ('pCube1|pCube3|pCube3', 'pCube4|pCube3|pCube3', 'pCube5|pCube3|pCube3'), ] for clique in expected_cliques: self.assertEqual(clique, self.sortedPathsTo(clique[0]))
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)