class ComboCmdTestCase(testTRSBase.TRSTestCaseBase): '''Verify the Transform3d UFE interface, for multiple runtimes. The Maya move, rotate, and scale commands is used to test setting object translation, rotation, and scale. As of 05-May-2020, object space relative moves, rotates, and scales are supported by Maya code, and move and rotate are supported in world space as well, although scale is not supported in world space. Object translation, rotation, and scale is read using the Transform3d interface and the native run-time interface. This test performs a sequence of the possible types of operations, and verifies that the position, rotation, and scale of the object has been modified according to how such operations should cumulate. The expected value consists of the translate, rotate, and scale vectors (in world space). It is computed by: - initializing the translate, rotate, and scale vectors - calling updateTRS after each operation; this method will reassemble the transformation matrix from the three vectors, apply the appropriate matrix transformation for the given operation, in the given space, and extract the translate, rotate, and scale vector, once again in world space. When a snapshot is taken for comparison purposes, the value extracted from the runtime objects is extracted for each component, and assembled into a vector that can be compared to the computed expected value vector. UFE Feature : Transform3d Maya Feature : move, rotate, scale Action : Relative move, rotate, and scale in object space; move, rotate in object space. Applied On Selection : - No selection - Given node as param - Single Selection [Maya, Non-Maya] Undo/Redo Test : Yes Expect Results To Test : - Maya Dag object world space position. - USD object world space position. Edge Cases : - None. ''' pluginsLoaded = False @classmethod def setUpClass(cls): if not cls.pluginsLoaded: cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() def setUp(self): ''' Called initially to set up the maya test environment ''' # Load plugins self.assertTrue(self.pluginsLoaded) # Set up memento, a list of snapshots. self.memento = [] # Callables to get current object translation and rotation using the # run-time and UFE. self.move = 0 self.rotate = 1 self.scale = 2 self.ops = [self.move, self.rotate, self.scale] self.runTimes = [None, None, None] self.ufes = [None, None, None] self.noSpace = None self.spaces = [om.MSpace.kObject, om.MSpace.kWorld] # Open top_layer.ma scene in test-samples mayaUtils.openTopLayerScene() # Create some extra Maya nodes cmds.polySphere() cmds.polyCube() # Clear selection to start off cmds.select(clear=True) def updateTRS(self, expectedTRS, op, v, space=om.MSpace.kWorld): '''Update the expected vector based on given operation, vector and space The expectedTRS vector has 9 entries: * 0-2 The world position * 3-5 The world rotation (in degrees) * 6-8 The world scale The possible operations are move, rotate, and scale. The possible spaces are kObject and kWorld (default) ''' if (expectedTRS is None): expectedTRS = [None] * 9 # trs starts as the identity matrix # trs = om.MTransformationMatrix() # Add translation, rotation, and scale, in world space, to recreate # the last transformation matrix. # if (expectedTRS[0] is not None): trs.setTranslation( om.MVector(expectedTRS[0], expectedTRS[1], expectedTRS[2]), om.MSpace.kWorld) if (expectedTRS[3] is not None): trs.setRotation( om.MEulerRotation(radians(expectedTRS[3]), radians(expectedTRS[4]), radians(expectedTRS[5]))) if (expectedTRS[6] is not None): trs.setScale( om.MVector(expectedTRS[6], expectedTRS[7], expectedTRS[8]), om.MSpace.kWorld) # Apply the requested operation. If the space is kObject, and we had a # scale factor, we must counteract it to get the right matrix, by # dividing the translation vector by it (otherwise it ends up being # scaled twice, and the expected value is incorrect). # if op == self.move: if (space == om.MSpace.kObject and expectedTRS[6] is not None): trs.translateBy( om.MVector(v[0] / expectedTRS[6], v[1] / expectedTRS[7], v[2] / expectedTRS[8]), space) else: trs.translateBy(om.MVector(v[0], v[1], v[2]), space) elif op == self.rotate: trs.rotateBy( om.MEulerRotation(radians(v[0]), radians(v[1]), radians(v[2])), space) elif op == self.scale: trs.scaleBy(om.MVector(v[0], v[1], v[2]), space) # Recover the world space translate, rotate, and scale, and updated # the expected vector # expectedTRS[0:3] = trs.translation(om.MSpace.kWorld) r = trs.rotation().asVector() expectedTRS[3] = degrees(r[0]) expectedTRS[4] = degrees(r[1]) expectedTRS[5] = degrees(r[2]) expectedTRS[6:9] = trs.scale(om.MSpace.kWorld) return expectedTRS def extractTRS(self, expectedTRS, op): '''Extract the move, rotate, or scale component ''' if op == self.move: # Translation (x, y, z) # return expectedTRS[0:3] elif op == self.rotate: # Rotation vector in degrees (x, y, z) # return expectedTRS[3:6] elif op == self.scale: # Scale (x, y, z) # return expectedTRS[6:9] def snapshotRunTimeUFE(self): '''Return a pair with an op read from the run-time and from UFE. Tests that the op read from the run-time interface matches the UFE op. ''' # Get translation # rtAll = None ufeAll = None offset = 0 for op in self.ops: runTimeVec = self.runTimes[op]() ufeVec = self.ufes[op]() if op == self.rotate: # The runtimes for rotate return an MEulerRotation, which we # must convert to a vector in degrees, since updateTRS expects # it in that format. # r = runTimeVec.asVector() rtAll = self.updateTRS( rtAll, op, [degrees(r[0]), degrees(r[1]), degrees(r[2])]) r = ufeVec.asVector() ufeAll = self.updateTRS( ufeAll, op, [degrees(r[0]), degrees(r[1]), degrees(r[2])]) else: rtAll = self.updateTRS(rtAll, op, runTimeVec) ufeAll = self.updateTRS(ufeAll, op, ufeVec) assertVectorAlmostEqual(self, runTimeVec, ufeVec) assertVectorAlmostEqual(self, rtAll, ufeAll) return (rtAll, ufeAll) def runTestCombo(self, expectedTRS): '''Engine method to run move, rotate, and scale test.''' # Save the initial values to the memento list. self.snapShotAndTest(expectedTRS, 6) # Do a combination of commands, and compare with expected. # Note: scale not supported in kObject space, hence, no test # rotate values are in degrees # ops = [[self.rotate, [10, 20, 30], om.MSpace.kObject], [self.move, [4, 5, 6], om.MSpace.kWorld], [self.move, [4, 5, 6], om.MSpace.kObject], [self.scale, [.1, 10, 100], om.MSpace.kObject], [self.rotate, [-10, -20, -30], om.MSpace.kWorld], [self.move, [-3, -2, -1], om.MSpace.kWorld], [self.scale, [1000, .01, .1], om.MSpace.kObject], [self.move, [-3, -2, -1], om.MSpace.kObject]] for item in ops: op = item[0] if (op not in self.ops): continue v = item[1] space = item[2] if (op == self.move): if (space == om.MSpace.kObject): cmds.move(v[0], v[1], v[2], relative=True, os=True, wd=True) else: cmds.move(v[0], v[1], v[2], relative=True) elif (op == self.rotate): if (space == om.MSpace.kObject): cmds.rotate(v[0], v[1], v[2], relative=True, os=True, forceOrderXYZ=True) else: cmds.rotate(v[0], v[1], v[2], relative=True, ws=True, forceOrderXYZ=True) elif (op == self.scale): if (space == om.MSpace.kObject): cmds.scale(v[0], v[1], v[2], relative=True) else: # scale is only supported in object space; if it is # eventually supported in world space, this would be the # command to emit: #cmds.scale(v[0], v[1], v[2], relative=True, ws=True) # Fail if we attempt to test this type of operation self.assertEqual(space, om.MSpace.kObject, 'scale only supported in object space') continue expectedTRS = self.updateTRS(expectedTRS, op, v, space) self.snapShotAndTest(expectedTRS, 6) # Test undo, redo. self.rewindMemento() self.fforwardMemento() def testComboMaya(self): '''Move, rotate, and scale Maya object, read through the Transform3d interface.''' # Give the sphere an initial position, rotation, scale, and select it. sphereObj = om.MSelectionList().add('pSphere1').getDagPath(0).node() sphereFn = om.MFnTransform(sphereObj) expectedTRS = None if (self.move in self.ops): expectedTRS = self.updateTRS(expectedTRS, self.move, [1, 2, 3]) t = self.extractTRS(expectedTRS, self.move) sphereFn.setTranslation(om.MVector(t[0], t[1], t[2]), om.MSpace.kTransform) if (self.rotate in self.ops): expectedTRS = self.updateTRS(expectedTRS, self.rotate, [30, 60, 90]) r = self.extractTRS(expectedTRS, self.rotate) sphereFn.setRotation( om.MEulerRotation(radians(r[0]), radians(r[1]), radians(r[2])), om.MSpace.kTransform) if (self.scale in self.ops): expectedTRS = self.updateTRS(expectedTRS, self.scale, [1, 2, 3]) s = self.extractTRS(expectedTRS, self.scale) sphereFn.setScale(om.MVector(s[0], s[1], s[2])) spherePath = ufe.Path(mayaUtils.createUfePathSegment("|pSphere1")) sphereItem = ufe.Hierarchy.createItem(spherePath) ufe.GlobalSelection.get().append(sphereItem) # Create a Transform3d interface for it. transform3d = ufe.Transform3d.transform3d(sphereItem) # Set up the callables that will retrieve the translation. self.runTimes[self.move] = partial(sphereFn.translation, om.MSpace.kTransform) self.ufes[self.move] = partial(transform3dTranslation, transform3d) # Set up the callables that will retrieve the rotation. self.runTimes[self.rotate] = partial(sphereFn.rotation, om.MSpace.kTransform) self.ufes[self.rotate] = partial(transform3dRotation, transform3d) # Set up the callables that will retrieve the scale. self.runTimes[self.scale] = partial(sphereFn.scale) self.ufes[self.scale] = partial(transform3dScale, transform3d) self.runTestCombo(expectedTRS) @unittest.skipUnless( mayaUtils.previewReleaseVersion() >= 115, 'Requires Maya fixes only available in Maya Preview Release 115 or later.' ) def testComboUSD(self): '''Move, rotate, and scale USD object, read through the Transform3d interface.''' # Select Ball_35 to move, rotate, and scale it. ball35Path = ufe.Path([ mayaUtils.createUfePathSegment("|world|transform1|proxyShape1"), usdUtils.createUfePathSegment("/Room_set/Props/Ball_35") ]) ball35Item = ufe.Hierarchy.createItem(ball35Path) ufe.GlobalSelection.get().append(ball35Item) # Create a Transform3d interface for it. transform3d = ufe.Transform3d.transform3d(ball35Item) # We compare the UFE ops with the USD run-time ops. To # obtain the full ops of Ball_35, we need to add the USD # ops to the Maya proxy shape ops. proxyShapeXformObj = om.MSelectionList().add('transform1').getDagPath( 0).node() proxyShapeXformFn = om.MFnTransform(proxyShapeXformObj) def ball35Translation(): ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item) return addVec(proxyShapeXformFn.translation(om.MSpace.kTransform), ball35Prim.GetAttribute('xformOp:translate').Get()) def ball35Rotation(): ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item) if not ball35Prim.HasAttribute('xformOp:rotateXYZ'): return proxyShapeXformFn.rotation(om.MSpace.kTransform) else: x, y, z = ball35Prim.GetAttribute('xformOp:rotateXYZ').Get() return proxyShapeXformFn.rotation( om.MSpace.kTransform) + om.MEulerRotation( radians(x), radians(y), radians(z)) def ball35Scale(): ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item) if not ball35Prim.HasAttribute('xformOp:scale'): return proxyShapeXformFn.scale() else: return combineScales( proxyShapeXformFn.scale(), ball35Prim.GetAttribute('xformOp:scale').Get()) # Set up the callables that will retrieve the translation. self.runTimes[self.move] = ball35Translation self.ufes[self.move] = partial(transform3dTranslation, transform3d) # Set up the callables that will retrieve the rotation. self.runTimes[self.rotate] = ball35Rotation self.ufes[self.rotate] = partial(transform3dRotation, transform3d) # Set up the callables that will retrieve the scale. self.runTimes[self.scale] = ball35Scale self.ufes[self.scale] = partial(transform3dScale, transform3d) # Save the initial position to the memento list. expectedTRS = None if (self.move in self.ops): v = ball35Translation() expectedTRS = self.updateTRS(expectedTRS, self.move, [v[0], v[1], v[2]]) if (self.rotate in self.ops): r = ball35Rotation().asVector() expectedTRS = self.updateTRS( expectedTRS, self.rotate, [degrees(r[0]), degrees(r[1]), degrees(r[2])]) if (self.scale in self.ops): s = ball35Scale() expectedTRS = self.updateTRS(expectedTRS, self.scale, [s[0], s[1], s[2]]) self.runTestCombo(expectedTRS)
class RotatePivotTestCase(unittest.TestCase): '''Verify the Transform3d UFE rotate pivot interface. UFE Feature : Transform3d Maya Feature : rotate pivot Action : Set the rotate pivot. Applied On Selection : - No selection - Given node as param - Single Selection : Not tested. - Multiple Selection [Mixed, Non-Maya] : Not tested. Undo/Redo Test : UFE undoable command only. Expect Results To Test : - Maya Dag object world space position. - USD object world space position. Edge Cases : - None. ''' pluginsLoaded = False @classmethod def setUpClass(cls): if not cls.pluginsLoaded: cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() def setUp(self): ''' Called initially to set up the maya test environment ''' # Load plugins self.assertTrue(self.pluginsLoaded) # Open twoSpheres.ma scene in test-samples mayaUtils.openTwoSpheresScene() def checkPos(self, m, p): self.assertAlmostEqual(m[ndx(3, 0)], p[0]) self.assertAlmostEqual(m[ndx(3, 1)], p[1]) self.assertAlmostEqual(m[ndx(3, 2)], p[2]) def testRotatePivot(self): # mayaSphere is at (10, 0, 10) in local space, and since it has no # parent, in world space as well. spherePath = om.MSelectionList().add('mayaSphere').getDagPath(0) sphereFn = om.MFnTransform(spherePath) rotZ = radians(45) rot = om.MEulerRotation(0, 0, rotZ) # Pivot around x=0. pivot = om.MPoint(-10, 0, 0) sphereFn.setRotatePivot(pivot, om.MSpace.kTransform, False) sphereFn.setRotation(rot, om.MSpace.kTransform) # MFnTransform and MTransformationMatrix always return the individual # components of the matrix, including translation, which is unaffected # by pivot changes, as expected. The fully-multiplied result is in the # computed MMatrix. xyWorldValue = sin(rotZ) * 10 sphereMatrix = sphereFn.transformation().asMatrix() self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 10]) # Do the same with the USD object, through UFE. # USD sphere is at (10, 0, 0) in local space, and since its parents # have an identity transform, in world space as well. usdSpherePath = ufe.PathString.path( '|usdSphereParent|usdSphereParentShape,/sphereXform/sphere') usdSphereItem = ufe.Hierarchy.createItem(usdSpherePath) t3d = ufe.Transform3d.transform3d(usdSphereItem) # if (ufeUtils.ufeFeatureSetVersion() >= 2): if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '2025'): t3d.rotatePivotTranslate(pivot[0], pivot[1], pivot[2]) else: t3d.rotatePivot(pivot[0], pivot[1], pivot[2]) usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), pivot) t3d.rotate(degrees(rot[0]), degrees(rot[1]), degrees(rot[2])) sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix) self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 0]) # if (ufeUtils.ufeFeatureSetVersion() >= 2): if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '2025'): t3d.rotatePivotTranslate(0, 0, 0) else: t3d.rotatePivot(0, 0, 0) usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(0, 0, 0)) sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix) self.checkPos(sphereMatrix, [10, 0, 0]) # Use a UFE undoable command to set the pivot. # if (ufeUtils.ufeFeatureSetVersion() >= 2): if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '2025'): rotatePivotCmd = t3d.rotatePivotTranslateCmd() rotatePivotCmd.translate(pivot[0], pivot[1], pivot[2]) else: rotatePivotCmd = t3d.rotatePivotCmd() rotatePivotCmd.set(pivot[0], pivot[1], pivot[2]) usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), pivot) sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix) self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 0]) rotatePivotCmd.undo() usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(0, 0, 0)) sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix) self.checkPos(sphereMatrix, [10, 0, 0]) # redo() cannot be tested, as it currently is intentionally not # implemented, because the Maya move command handles undo by directly # calling the translate() method. This is fragile, # implementation-specific, and should be changed. PPT, 3-Sep-2020. @unittest.skipUnless( mayaUtils.previewReleaseVersion() >= 119, 'Requires Maya fixes only available in Maya Preview Release 119 or later.' ) def testRotatePivotCmd(self): rotZ = radians(45) rot = om.MEulerRotation(0, 0, rotZ) # Pivot around x=0. pivot = om.MPoint(-10, 0, 0) xyWorldValue = sin(rotZ) * 10 # USD sphere is at (10, 0, 0) in local space, and since its parents # have an identity transform, in world space as well. spherePath = ufe.PathString.path( '|usdSphereParent|usdSphereParentShape,/sphereXform/sphere') sphereItem = ufe.Hierarchy.createItem(spherePath) ufe.GlobalSelection.get().append(sphereItem) # Create a Transform3d interface to read from USD. t3d = ufe.Transform3d.transform3d(sphereItem) # Start with a non-zero initial rotate pivot. This is required to test # MAYA-105345, otherwise a zero initial rotate pivot produces the # correct result through an unintended code path. # if (ufeUtils.ufeFeatureSetVersion() >= 2): if (os.getenv('UFE_PREVIEW_VERSION_NUM', '0000') < '2025'): t3d.rotatePivotTranslate(2, 0, 0) else: t3d.rotatePivot(2, 0, 0) usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(2, 0, 0)) cmds.move(-12, 0, 0, relative=True, ufeRotatePivot=True) usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), pivot) cmds.undo() usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), om.MPoint(2, 0, 0)) cmds.redo() usdPivot = t3d.rotatePivot() self.assertEqual(v3dToMPoint(usdPivot), pivot) cmds.rotate(degrees(rot[0]), degrees(rot[1]), degrees(rot[2])) sphereMatrix = om.MMatrix(t3d.inclusiveMatrix().matrix) self.checkPos(sphereMatrix, [xyWorldValue, xyWorldValue, 0])
class MoveCmdTestCase(testTRSBase.TRSTestCaseBase): '''Verify the Transform3d UFE interface, for multiple runtimes. The Maya move command is used to test setting object translation. As of 27-Mar-2018, only world space relative moves are supported by Maya code. Object translation is read using the Transform3d interface and the native run-time interface. UFE Feature : Transform3d Maya Feature : move Action : Relative move in world space. Applied On Selection : - No selection - Given node as param - Single Selection [Maya, Non-Maya] - Multiple Selection [Mixed, Non-Maya]. Maya-only selection tested by Maya. Undo/Redo Test : Yes Expect Results To Test : - Maya Dag object world space position. - USD object world space position. Edge Cases : - None. ''' pluginsLoaded = False @classmethod def setUpClass(cls): if not cls.pluginsLoaded: cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() def setUp(self): ''' Called initially to set up the maya test environment ''' # Load plugins self.assertTrue(self.pluginsLoaded) # Set up memento, a list of snapshots. self.memento = [] # Callables to get current object translation using the run-time and # UFE. self.runTimeTranslation = None self.ufeTranslation = None # Open top_layer.ma scene in test-samples mayaUtils.openTopLayerScene() # Create some extra Maya nodes cmds.polySphere() cmds.polyCube() # Clear selection to start off cmds.select(clear=True) def snapshotRunTimeUFE(self): '''Return a pair with translation read from the run-time and from UFE. Tests that the translation read from the run-time interface matches the UFE translation. ''' runTimeVec = self.runTimeTranslation() ufeVec = self.ufeTranslation() assertVectorAlmostEqual(self, runTimeVec, ufeVec) return (runTimeVec, ufeVec) def multiSelectSnapshotRunTimeUFE(self, items): '''Return a list of pairs with translation read from the run-time and from UFE. Tests that the translation read from the run-time interface matches the UFE translation. ''' snapshot = [] for item in items: runTimeVec = self.runTimeTranslation(item) ufeVec = self.ufeTranslation(item) assertVectorAlmostEqual(self, runTimeVec, ufeVec) snapshot.append((runTimeVec, ufeVec)) return snapshot def runTestMove(self, expected): '''Engine method to run move test.''' # Save the initial position to the memento list. self.snapShotAndTest(expected) # Do some move commands, and compare with expected. for relativeMove in [om.MVector(4, 5, 6), om.MVector(-3, -2, -1)]: cmds.move(*relativeMove, relative=True) expected += relativeMove self.snapShotAndTest(expected) # Test undo, redo. self.rewindMemento() self.fforwardMemento() def runMultiSelectTestMove(self, items, expected): '''Engine method to run multiple selection move test.''' # Save the initial positions to the memento list. self.multiSelectSnapShotAndTest(items, expected) # Do some move commands, and compare with expected. for relativeMove in [om.MVector(4, 5, 6), om.MVector(-3, -2, -1)]: cmds.move(*relativeMove, relative=True) expected = multiSelectAddTranslations(expected, relativeMove) self.multiSelectSnapShotAndTest(items, expected) # Test undo, redo. self.multiSelectRewindMemento(items) self.multiSelectFforwardMemento(items) def testMoveMaya(self): '''Move Maya object, read through the Transform3d interface.''' # Give the sphere an initial position, and select it. expected = om.MVector(1, 2, 3) sphereObj = om.MSelectionList().add('pSphere1').getDagPath(0).node() sphereFn = om.MFnTransform(sphereObj) sphereFn.setTranslation(expected, om.MSpace.kTransform) spherePath = ufe.Path(mayaUtils.createUfePathSegment("|pSphere1")) sphereItem = ufe.Hierarchy.createItem(spherePath) ufe.GlobalSelection.get().append(sphereItem) # Create a Transform3d interface for it. transform3d = ufe.Transform3d.transform3d(sphereItem) # Set up the callables that will retrieve the translation. self.runTimeTranslation = partial(sphereFn.translation, om.MSpace.kTransform) self.ufeTranslation = partial(transform3dTranslation, transform3d) self.runTestMove(expected) @unittest.skipUnless( mayaUtils.previewReleaseVersion() >= 115, 'Requires Maya fixes only available in Maya Preview Release 115 or later.' ) def testMoveUSD(self): '''Move USD object, read through the Transform3d interface.''' # Select Ball_35 to move it. ball35Path = ufe.Path([ mayaUtils.createUfePathSegment("|world|transform1|proxyShape1"), usdUtils.createUfePathSegment("/Room_set/Props/Ball_35") ]) ball35Item = ufe.Hierarchy.createItem(ball35Path) ufe.GlobalSelection.get().append(ball35Item) # Create a Transform3d interface for it. transform3d = ufe.Transform3d.transform3d(ball35Item) # We compare the UFE translation with the USD run-time translation. To # obtain the full translation of Ball_35, we need to add the USD # translation to the Maya proxy shape translation. proxyShapeXformObj = om.MSelectionList().add('transform1').getDagPath( 0).node() proxyShapeXformFn = om.MFnTransform(proxyShapeXformObj) def ball35Translation(): ball35Prim = usdUtils.getPrimFromSceneItem(ball35Item) return addVec(proxyShapeXformFn.translation(om.MSpace.kTransform), ball35Prim.GetAttribute('xformOp:translate').Get()) # Set up the callables that will retrieve the translation. self.runTimeTranslation = ball35Translation self.ufeTranslation = partial(transform3dTranslation, transform3d) # Save the initial position to the memento list. expected = ball35Translation() self.runTestMove(expected) @unittest.skipUnless( mayaUtils.previewReleaseVersion() >= 115, 'Requires Maya fixes only available in Maya Preview Release 115 or later.' ) def testMultiSelectMoveUSD(self): '''Move multiple USD objects, read through Transform3d interface.''' # Select multiple balls to move them. proxyShapePathSegment = mayaUtils.createUfePathSegment( "|world|transform1|proxyShape1") balls = ['Ball_33', 'Ball_34'] ballPaths = [ ufe.Path([ proxyShapePathSegment, usdUtils.createUfePathSegment('/Room_set/Props/' + ball) ]) for ball in balls ] ballItems = [ ufe.Hierarchy.createItem(ballPath) for ballPath in ballPaths ] for ballItem in ballItems: ufe.GlobalSelection.get().append(ballItem) # We compare the UFE translation with the USD run-time translation. To # obtain the full translation of USD scene items, we need to add the USD # translation to the Maya proxy shape translation. proxyShapeXformObj = om.MSelectionList().add('transform1').getDagPath( 0).node() proxyShapeXformFn = om.MFnTransform(proxyShapeXformObj) def usdSceneItemTranslation(item): prim = usdUtils.getPrimFromSceneItem(item) if not prim.HasAttribute('xformOp:translate'): return proxyShapeXformFn.translation(om.MSpace.kTransform) else: return addVec( proxyShapeXformFn.translation(om.MSpace.kTransform), prim.GetAttribute('xformOp:translate').Get()) def ufeSceneItemTranslation(item): return transform3dTranslation(ufe.Transform3d.transform3d(item)) # Set up the callables that will retrieve the translation. self.runTimeTranslation = usdSceneItemTranslation self.ufeTranslation = ufeSceneItemTranslation # Give the tail item in the selection an initial translation that # is different, to catch bugs where the relative translation # incorrectly squashes any existing translation. backItem = ballItems[-1] backT3d = ufe.Transform3d.transform3d(backItem) initialTranslation = [-10, -20, -30] backT3d.translate(*initialTranslation) assertVectorAlmostEqual(self, ufeSceneItemTranslation(backItem), usdSceneItemTranslation(backItem)) # Save the initial positions to the memento list. expected = [ usdSceneItemTranslation(ballItem) for ballItem in ballItems ] self.runMultiSelectTestMove(ballItems, expected)
def test_pickWalk(self): ''' This test will be verify the use of Maya's pickWalk into Maya objects and Usd objects. ''' # Initial state check up self.snapShotAndTest() # No Selection cmds.pickWalk("pSphere1", d="down") self.snapShotAndTest() # Single Selection - Maya Item cmds.select("pCube1") self.snapShotAndTest() cmds.pickWalk(d="down") self.snapShotAndTest() # Edge Case: Maya item to UFE Item cmds.select("proxyShape1") self.snapShotAndTest() cmds.pickWalk(d="down") # /Room_set self.snapShotAndTest(([], ['Room_set'])) # Edge Case: UFE Item to Maya cmds.pickWalk(d="up") # /proxyShape1 self.snapShotAndTest() # Pickwalk all the way down in USD - Pickwalk down again for usdItem in ['Room_set', 'Props', 'Ball_1', 'mesh']: cmds.pickWalk(d="down") # Room_set / Props / Ball_1 / mesh self.snapShotAndTest(([], [usdItem])) # Edge Case : Down on last item (Down again) cmds.pickWalk(d="down") self.snapShotAndTest(([], ['mesh'])) # Edge Case : Up on top item (Up again) cmds.select("transform1") self.snapShotAndTest() cmds.pickWalk(d="up") # transform1 self.snapShotAndTest() # World is not selectable in maya # Pickwalk on unsupported UFE items cmds.select("pCube1.e[6]") if int(cmds.about(majorVersion=True) ) <= 2020 or mayaUtils.previewReleaseVersion() < 115: self.snapShotAndTest((["pCube1.e[6]"], ["pCubeShape1"])) self.assertTrue(next(iter(ufe.GlobalSelection.get())).isProperty()) else: self.snapShotAndTest((["pCube1.e[6]"], [])) self.assertTrue(ufe.GlobalSelection.get().empty()) # TODO: HS 2019 : test fails. MAYA-101373 # cmds.pickWalk(type="edgeloop", d="right") # import pdb; pdb.set_trace() # self.snapShotAndTest((["pCube1.e[10]"], ["pCubeShape1"])) # self.assertTrue(next(iter(ufe.GlobalSelection.get())).isProperty()) # pCubeShape1 in ufe selection is a property # Pickwalk on Non-Mixed Maya items is already tested in Maya regression tests # Pickwalk on Non-Mixed USD items ufeUtils.selectPath(ufe.Path([ \ mayaUtils.createUfePathSegment("|world|transform1|proxyShape1"), \ usdUtils.createUfePathSegment("/Room_set/Props/Ball_1") \ ]), True) self.snapShotAndTest(([], ['Ball_1'])) ufeUtils.selectPath(ufe.Path([ \ mayaUtils.createUfePathSegment("|world|transform1|proxyShape1"), \ usdUtils.createUfePathSegment("/Room_set/Props/Ball_2") \ ])) self.snapShotAndTest(([], ['Ball_1', 'Ball_2'])) cmds.pickWalk(d="down") self.snapShotAndTest(([], ['mesh', 'mesh'])) # Pickwalk on mixed items ufeUtils.selectPath(ufe.Path([ \ mayaUtils.createUfePathSegment("|world|transform1|proxyShape1"), \ usdUtils.createUfePathSegment("/Room_set/Props/Ball_1") \ ]), True) self.snapShotAndTest(([], ['Ball_1'])) ufeUtils.selectPath(ufe.Path( mayaUtils.createUfePathSegment("|pCube1"))) self.snapShotAndTest((['pCube1'], ['Ball_1', 'pCube1'])) # Selection on Maya items from UFE does not work cmds.pickWalk(d="down") self.snapShotAndTest((['pCubeShape1'], ['mesh', 'pCubeShape1'])) # Test Complete - Undo and redo everything ''' MAYA-92081 : Undoing cmds.select does not restore the UFE selection.
class SelectTestCase(unittest.TestCase): '''Verify UFE selection on a USD scene.''' pluginsLoaded = False @classmethod def setUpClass(cls): if not cls.pluginsLoaded: cls.pluginsLoaded = mayaUtils.isMayaUsdPluginLoaded() @classmethod def tearDownClass(cls): cmds.file(new=True, force=True) def setUp(self): # Load plugins self.assertTrue(self.pluginsLoaded) # Load a file that has the same scene in both the Maya Dag # hierarchy and the USD hierarchy. filePath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test-samples", "parentCmd", "simpleSceneMayaPlusUSD_TRS.ma") cmds.file(filePath, force=True, open=True) # Clear selection to start off cmds.select(clear=True) def runTestSelection(self, selectCmd): '''Run the replace selection test, using the argument command to replace the selection with a single scene item.''' # Create multiple scene items. We will alternate between selecting a # Maya item and a USD item, 3 items each data model, one item at a # time, so we select 6 different items, one at a time. shapeSegment = mayaUtils.createUfePathSegment( "|world|mayaUsdProxy1|mayaUsdProxyShape1") names = ["pCube1", "pCylinder1", "pSphere1"] usdPaths = [] for n in ["/" + o for o in names]: usdPaths.append( ufe.Path([shapeSegment, usdUtils.createUfePathSegment(n)])) mayaPaths = [] for n in ["|world|" + o for o in names]: mayaPaths.append(ufe.Path(mayaUtils.createUfePathSegment(n))) # Create a list of paths by alternating USD objects and Maya objects # Flatten zipped tuples using list comprehension double loop. zipped = zip(usdPaths, mayaPaths) paths = [j for i in zipped for j in i] # Create items for all paths. items = [ufe.Hierarchy.createItem(p) for p in paths] # Clear the selection. globalSn = ufe.GlobalSelection.get() globalSn.clear() self.assertTrue(globalSn.empty()) # Select all items in turn. for item in items: selectCmd(item) # Item in the selection should be the last item in our list. self.assertEqual(len(globalSn), 1) def snFront(sn): return next(iter(sn)) if \ ufe.VersionInfo.getMajorVersion() == 1 else sn.front() self.assertEqual(snFront(globalSn), items[-1]) # Check undo. For this purpose, re-create the list of items in reverse # order. Because we're already at the last item, we skip the last one # (i.e. last item is -1, so start at -2, and increment by -1). rItems = items[-2::-1] # Undo until the first element, checking the selection as we go. for i in rItems: cmds.undo() self.assertEqual(len(globalSn), 1) self.assertEqual(snFront(globalSn), i) # Check redo. fItems = items[1:] # Redo until the last element, checking the selection as we go. for i in fItems: cmds.redo() self.assertEqual(len(globalSn), 1) self.assertEqual(snFront(globalSn), i) def testUfeSelect(self): def selectCmd(item): sn = ufe.Selection() sn.append(item) ufeSelectCmd.replaceWith(sn) self.runTestSelection(selectCmd) @unittest.skipUnless( mayaUtils.previewReleaseVersion() >= 116, 'Requires Maya fixes only available in Maya Preview Release 116 or later.' ) def testMayaSelect(self): # At time of writing (17-May-2020), Maya select command does not # accept UFE path strings. Use Maya select for Maya scene items, # and UFE select for non-Maya scene items. def selectCmd(item): if item.runTimeId() == 1: # Single path segment. Simply pop the |world head of path. p = str(item.path().popHead()) cmds.select(p) else: sn = ufe.Selection() sn.append(item) ufeSelectCmd.replaceWith(sn) self.runTestSelection(selectCmd)
def testDeleteArgs(self): '''Delete Maya and USD objects passed as command arguments.''' spherePath = ufe.Path(mayaUtils.createUfePathSegment("|pSphere1")) sphereItem = ufe.Hierarchy.createItem(spherePath) sphereShapePath = ufe.Path( mayaUtils.createUfePathSegment("|pSphere1|pSphereShape1")) sphereShapeItem = ufe.Hierarchy.createItem(sphereShapePath) mayaSegment = mayaUtils.createUfePathSegment( "|world|transform1|proxyShape1") ball35Path = ufe.Path([ mayaSegment, usdUtils.createUfePathSegment("/Room_set/Props/Ball_35") ]) ball35Item = ufe.Hierarchy.createItem(ball35Path) ball34Path = ufe.Path([ mayaSegment, usdUtils.createUfePathSegment("/Room_set/Props/Ball_34") ]) ball34Item = ufe.Hierarchy.createItem(ball34Path) propsPath = ufe.Path( [mayaSegment, usdUtils.createUfePathSegment("/Room_set/Props")]) propsItem = ufe.Hierarchy.createItem(propsPath) sphereShapeName = str(sphereShapeItem.path().back()) ball35Name = str(ball35Item.path().back()) ball34Name = str(ball34Item.path().back()) # Before delete, each item is a child of its parent. sphereHierarchy = ufe.Hierarchy.hierarchy(sphereItem) propsHierarchy = ufe.Hierarchy.hierarchy(propsItem) sphereChildren = sphereHierarchy.children() propsChildren = propsHierarchy.children() sphereChildrenNames = childrenNames(sphereChildren) propsChildrenNames = childrenNames(propsChildren) self.assertIn(sphereShapeItem, sphereChildren) self.assertIn(ball35Item, propsChildren) self.assertIn(sphereShapeName, sphereChildrenNames) self.assertIn(ball35Name, propsChildrenNames) ball34PathString = ufe.PathString.string(ball34Path) self.assertEqual( ball34PathString, "|transform1|proxyShape1,/Room_set/Props/Ball_34" if \ mayaUtils.previewReleaseVersion() >= 116 else \ "|world|transform1|proxyShape1,/Room_set/Props/Ball_34") # Test that "|world" prefix is optional for multi-segment paths. ball35PathString = "|transform1|proxyShape1,/Room_set/Props/Ball_35" cmds.delete(ball35PathString, ball34PathString, "|pSphere1|pSphereShape1") sphereChildren = sphereHierarchy.children() propsChildren = propsHierarchy.children() sphereChildrenNames = childrenNames(sphereChildren) propsChildrenNames = childrenNames(propsChildren) self.assertNotIn(sphereShapeName, sphereChildrenNames) self.assertNotIn(ball35Name, propsChildrenNames) self.assertNotIn(ball34Name, propsChildrenNames) self.assertFalse(cmds.ls("|pSphere1|pSphereShape1")) cmds.undo() sphereChildren = sphereHierarchy.children() propsChildren = propsHierarchy.children() sphereChildrenNames = childrenNames(sphereChildren) propsChildrenNames = childrenNames(propsChildren) self.assertIn(sphereShapeItem, sphereChildren) self.assertIn(ball35Item, propsChildren) self.assertIn(ball34Item, propsChildren) self.assertIn(sphereShapeName, sphereChildrenNames) self.assertIn(ball35Name, propsChildrenNames) self.assertIn(ball34Name, propsChildrenNames) self.assertTrue(cmds.ls("|pSphere1|pSphereShape1")) cmds.redo() sphereChildren = sphereHierarchy.children() propsChildren = propsHierarchy.children() sphereChildrenNames = childrenNames(sphereChildren) propsChildrenNames = childrenNames(propsChildren) self.assertNotIn(sphereShapeName, sphereChildrenNames) self.assertNotIn(ball35Name, propsChildrenNames) self.assertNotIn(ball34Name, propsChildrenNames) self.assertFalse(cmds.ls("|pSphere1|pSphereShape1"))