def _TestPrototypes(self, instancerName): """ Tests that all of the instancer prototypes made it to USD. """ self.assertTrue(self.stage) instancer = OMFX.MFnInstancer(self._GetDagPath(instancerName)) # Move to the last frame so that we can ensure all of the prototypes # are in use. cmds.currentTime(self.END_TIMECODE, edit=True) paths = OM.MDagPathArray() matrices = OM.MMatrixArray() particlePathStartIndices = OM.MIntArray() pathIndices = OM.MIntArray() instancer.allInstances(paths, matrices, particlePathStartIndices, pathIndices) # Check that the Maya instanced objects are what we think they are. pathStrings = [paths[i].fullPathName() for i in range(paths.length())] self.assertEqual( pathStrings, [ # 0 (logical 0) - Cube "|dummyGroup|pCube1|pCubeShape1", "|dummyGroup|pCube1|pSphere1|pSphereShape1", # 1 (logical 2) - Sphere "|InstancerTest|%s|prototypeUnderInstancer|prototypeUnderInstancerShape" % instancerName, # 2 (logical 3) - Reference "|referencePrototype|NS_referencePrototype:Geom|NS_referencePrototype:Cone|NS_referencePrototype:ConeShape" ]) # Check that the USD prims have correct type name, references, kinds, # kinds, instancerTranslate xformOps. instancerPrim = self.stage.GetPrimAtPath("/InstancerTest/%s" % instancerName) self.assertEqual( Usd.ModelAPI(instancerPrim).GetKind(), Kind.Tokens.subcomponent) prototypesPrim = instancerPrim.GetChild("Prototypes") self.assertEqual(len(prototypesPrim.GetChildren()), 3) self.assertEqual( Usd.ModelAPI(prototypesPrim).GetKind(), Kind.Tokens.subcomponent) # Note that pCube1_0 is a special case where instancerTranslate # isn't the opposite of translate, so both have to be left in. prototype0 = prototypesPrim.GetChild("pCube1_0") self._AssertPrototype(prototype0, "Xform", 2, True) prototype1 = prototypesPrim.GetChild("prototypeUnderInstancer_1") self._AssertPrototype(prototype1, "Mesh", 0, False) prototype2 = prototypesPrim.GetChild("referencePrototype_2") self._AssertPrototype(prototype2, "Xform", 1, False) self.assertEqual( Usd.ModelAPI(prototype2).GetAssetName(), "ConeAssetName")
def testTransforms(self): """ Check that the point transforms are correct. """ mayaInstancer = OMFX.MFnInstancer(self._GetDagPath("instancer1")) usdInstancer = UsdGeom.PointInstancer( self.stage.GetPrimAtPath("/InstancerTest/instancer1")) time = self.START_TIMECODE while time <= self.END_TIMECODE: cmds.currentTime(time, edit=True) # Need to do this because MFnInstancer will give instance matrices # as offsets from prototypes' original world space positions. worldPositions = [ self._GetWorldSpacePosition("|dummyGroup|pCube1"), self._GetWorldSpacePosition( "|InstancerTest|instancer1|prototypeUnderInstancer"), self._GetWorldSpacePosition("|referencePrototype") ] paths = OM.MDagPathArray() matrices = OM.MMatrixArray() particlePathStartIndices = OM.MIntArray() pathIndices = OM.MIntArray() mayaInstancer.allInstances(paths, matrices, particlePathStartIndices, pathIndices) usdInstanceTransforms = \ usdInstancer.ComputeInstanceTransformsAtTime(time, time) usdProtoIndices = usdInstancer.GetProtoIndicesAttr().Get(time) self.assertEqual(matrices.length(), len(usdInstanceTransforms)) # Compute the instancer-space position of instances in Maya # (including the protos' transforms). By default, this is what # UsdGeomPointInstancer::ComputeInstanceTransformsAtTime already # gives us. mayaWorldPositions = [ worldPositions[protoIndex] for protoIndex in usdProtoIndices ] mayaGfMatrices = [ mayaWorldPositions[i] * self._MayaToGfMatrix(matrices[i]) for i in xrange(matrices.length()) ] usdGfMatrices = [ usdInstanceTransforms[i] for i in xrange(len(usdInstanceTransforms)) ] for i in xrange(len(usdGfMatrices)): self._AssertXformMatrices(mayaGfMatrices[i], usdGfMatrices[i]) time += 1.0
def testInstancePaths(self): """ Checks that the proto index assigned for each point is correct. """ mayaInstancer = OMFX.MFnInstancer(self._GetDagPath("instancer1")) usdInstancer = UsdGeom.PointInstancer( self.stage.GetPrimAtPath("/InstancerTest/instancer1")) time = self.START_TIMECODE while time <= self.END_TIMECODE: cmds.currentTime(time, edit=True) paths = OM.MDagPathArray() matrices = OM.MMatrixArray() particlePathStartIndices = OM.MIntArray() pathIndices = OM.MIntArray() mayaInstancer.allInstances(paths, matrices, particlePathStartIndices, pathIndices) usdProtoIndices = usdInstancer.GetProtoIndicesAttr().Get(time) # Mapping of proto index to index(es) in the paths array. # Note that in the Maya instancer a single point may map to multiple # DAG paths, which correspond to all the shapes in the instanced # hierarchy. usdIndicesToMayaIndices = { 0: [0, 1], # the first prototype has two shapes in hierarchy 1: [2], # this prototype only has one shape 2: [3], # the reference prototype only has one shape } for i in xrange(len(usdProtoIndices)): usdProtoIndex = usdProtoIndices[i] expectedMayaIndices = usdIndicesToMayaIndices[usdProtoIndex] mayaIndicesStart = particlePathStartIndices[i] mayaIndicesEnd = particlePathStartIndices[i + 1] self.assertEqual(mayaIndicesEnd - mayaIndicesStart, len(expectedMayaIndices)) actualPathIndices = [ pathIndices[i] for i in xrange(mayaIndicesStart, mayaIndicesEnd) ] self.assertEqual(actualPathIndices, expectedMayaIndices) time += 1.0
def bake(instancerName, start, end, progress=None): """ Process an instancer node over the specified frame range, reading the positions and objects. With this data objects are duplicated and positioned to mimic the instancer. With the particle data individual objects can be matched over the different frames and animated. When a particle "dies", the visibility is turned off. :param str instancerName: Name if the instancer to bake :param int start: Start frame :param int end: End frame :param QProgressBar progress: Update ui ( Optional ) :return: Group that contains all of the baked information :rtype: str :raises RuntimeError: When the instancer doesn't exist :raises RuntimeError: When there are no particles attached """ # store all particle information in data variable data = {} # get instance if not cmds.objExists(instancerName): raise RuntimeError("Instancer doesn't exist!") return # set visible cmds.setAttr("{0}.visibility".format(instancerName), 1) instancerObj = asMObject(instancerName) instancerDag = asMDagPath(instancerObj) instancer = OpenMayaFX.MFnInstancer(instancerDag) # get particles particleName = cmds.listConnections( "{0}.inputPoints".format(instancerName)) if not particleName: raise RuntimeError("No particles connected!") return particleName = particleName[0] particleObj = asMObject(particleName) particleDag = asMDagPath(particleObj) particle = OpenMayaFX.MFnParticleSystem(particleDag) # variables ages = OpenMaya.MDoubleArray() paths = OpenMaya.MDagPathArray() matrices = OpenMaya.MMatrixArray() particleIndices = OpenMaya.MIntArray() pathIndices = OpenMaya.MIntArray() # create container group container = cmds.group(world=True, empty=True, n="{0}_bake_1".format(instancerName)) # loop time for i in range(start, end + 1): # set time cmds.currentTime(i) # query instancer information instancer.allInstances(paths, matrices, particleIndices, pathIndices) # query particle information particle.age(ages) # loop particle instances num = matrices.length() for j in range(num): # get particles index p = particleIndices[j] # get parent parent = paths[pathIndices[j]].fullPathName() # get age age = ages[j] if data.get(p): # get path and age path = data[p].get("path") oldAge = data[p].get("age") oldParent = data[p].get("parent") # check if age is less than previous if age < oldAge: # hide mesh and delete particle id data keyVisibility(path, i, 0) del (data[p]) # check if parent is the same as previous elif parent != oldParent: # hide mesh and delete particle id data keyVisibility(path, i, 0) del (data[p]) # duplicate path if index not in data if not data.get(p): # get parent name parentShort = parent.split("|")[-1].split(":")[-1] # duplicate mesh name = "{0}_{1}_1".format(instancerName, parentShort) path = cmds.duplicate(parent, n=name)[0] path = cmds.parent(path, container)[0] # handle visibility keyVisibility(path, i, 1) # get dag dag = asMDagPath(asMObject(path)) # get matrix transform = asMFnTransform(dag) matrix = transform.transformation().asMatrix() # store variables data[p] = {} data[p]["path"] = path data[p]["dag"] = dag data[p]["matrix"] = matrix data[p]["parent"] = parent # get variables path = data[p].get("path") dag = data[p].get("dag") matrix = data[p].get("matrix") # store age data[p]["age"] = age # set matrix m = matrix * matrices[j] m = OpenMaya.MTransformationMatrix(m) transform = asMFnTransform(dag) transform.set(m) # set keyframes keyTransform(path, i) # update progress if progress: progress.setValue(i + 1 - start) return container