def __create_soft_ik(self, ik_control): """Create the node network to allow soft ik :param ik_control: Name of the ik control :return: The attribute containing the percentage length from start joint to handle that the ik should be placed """ cmds.addAttr( ik_control, ln="softIk", minValue=0.0, maxValue=1.0, defaultValue=0.0, keyable=True, ) softik = dge("clamp(x, 0.001, 1)", x="{}.softIk".format(ik_control)) # We need to adjust how far the ik handle is from the start to create the soft # effect soft_ik_percentage = dge( "x > (1.0 - softIk)" "? (1.0 - softIk) + softIk * (1.0 - exp(-(x - (1.0 - softIk)) / softIk)) " ": x", container="{}_softik".format(self.name), x=self.percent_rest_distance, softIk=softik, ) return soft_ik_percentage
def test_cos(self): loc = cmds.spaceLocator(name="cos")[0] loc2 = cmds.spaceLocator()[0] dge("y=cos(x)", x="{}.tx".format(loc), y="{}.ty".format(loc2)) for i in range(100): v = -10.0 + 0.1 * i cmds.setAttr("{}.tx".format(loc), v) y = cmds.getAttr("{}.ty".format(loc2)) self.assertAlmostEquals(y, math.cos(v), places=5)
def test_sin(self): loc = cmds.spaceLocator(name="sin")[0] loc2 = cmds.spaceLocator()[0] dge("y=sin(x)", x="{}.tx".format(loc), y="{}.ty".format(loc2)) for i in range(100): v = -math.pi * 0.5 + 0.1 * i cmds.setAttr("{}.tx".format(loc), v) y = cmds.getAttr("{}.ty".format(loc2)) self.assertAlmostEquals(y, math.sin(v), places=5)
def test_acos(self): loc = cmds.spaceLocator(name="acos")[0] loc2 = cmds.spaceLocator()[0] dge("y=acos(x)", x="{}.tx".format(loc), y="{}.ty".format(loc2)) for i in range(101): v = -1.0 + 0.02 * i cmds.setAttr("{}.tx".format(loc), v) y = cmds.getAttr("{}.ty".format(loc2)) self.assertAlmostEquals(y, math.degrees(math.acos(v)), places=4)
def test_atan(self): loc = cmds.spaceLocator(name="atan")[0] loc2 = cmds.spaceLocator()[0] dge("y=atan(x)", x="{}.tx".format(loc), y="{}.ty".format(loc2)) for i in range(100): v = -5.0 + 0.1 * i cmds.setAttr("{}.tx".format(loc), v) y = cmds.getAttr("{}.ty".format(loc2)) self.assertAlmostEquals(y, math.degrees(math.atan(v)), places=5)
def test_tan(self): loc = cmds.spaceLocator(name="tan")[0] loc2 = cmds.spaceLocator()[0] dge("y=tan(x)", x="{}.tx".format(loc), y="{}.ty".format(loc2)) v = -math.pi * 0.5 + 0.02 while v < math.pi * 0.5: v += 0.02 cmds.setAttr("{}.tx".format(loc), v) y = cmds.getAttr("{}.ty".format(loc2)) self.assertAlmostEquals(y, math.tan(v), places=1)
def test_abs(self): loc = cmds.spaceLocator()[0] dge("y=abs(x)", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 0.75) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 0.75) cmds.setAttr("{}.tx".format(loc), -0.75) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 0.75)
def test_max(self): loc = cmds.spaceLocator()[0] dge("y=max(x, 2)", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 1) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 2) cmds.setAttr("{}.tx".format(loc), 4) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 4)
def test_lerp(self): loc = cmds.spaceLocator()[0] dge("y=lerp(4, 8, x)", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 1) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 8) cmds.setAttr("{}.tx".format(loc), 0) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 4) cmds.setAttr("{}.tx".format(loc), 0.25) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 5)
def test_add_two_attrs(self): loc = cmds.spaceLocator()[0] result = dge("x+x", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 10.0)
def test_exp(self): loc = cmds.spaceLocator()[0] result = dge("exp(x)", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, math.exp(5), places=3)
def test_pow(self): loc = cmds.spaceLocator()[0] result = dge("x^2", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 25)
def test_distance(self): loc = cmds.spaceLocator()[0] loc2 = cmds.spaceLocator()[0] cmds.setAttr("{}.tx".format(loc), 2.5) result = dge("distance(i, j)", container="mydistance", i=loc, j=loc2) d = cmds.getAttr(result) self.assertAlmostEquals(d, 2.5)
def test_parentheses(self): loc = cmds.spaceLocator()[0] result = dge("(x+3)*(2+x)", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 56.0)
def test_subtract_1_to_3(self): loc = cmds.spaceLocator()[0] result = dge("3-x", x="{}.t".format(loc)) cmds.connectAttr(result, "{}.r".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.rx".format(loc)) self.assertAlmostEquals(y, -2.0, places=6)
def test_add_3_to_1(self): loc = cmds.spaceLocator()[0] result = dge("x+3", x="{}.t".format(loc)) cmds.connectAttr(result, "{}.r".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.rx".format(loc)) self.assertAlmostEquals(y, 8.0, places=6)
def test_two_op_add_subtract(self): loc = cmds.spaceLocator()[0] result = dge("x+3-y", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.connectAttr(result, "{}.tz".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) cmds.setAttr("{}.ty".format(loc), 2) z = cmds.getAttr("{}.tz".format(loc)) self.assertAlmostEquals(z, 6.0)
def test_ternary_is_equal(self): loc = cmds.spaceLocator()[0] result = dge("x == 1 ? x : 4", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 4) cmds.setAttr("{}.tx".format(loc), 1) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 1)
def test_ternary_with_function(self): loc = cmds.spaceLocator()[0] result = dge("x < 1 ? x : exp(x)", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, math.exp(5), places=3) cmds.setAttr("{}.tx".format(loc), 0) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 0)
def test_reuse_nodes(self): loc = cmds.spaceLocator()[0] result = dge("y=(1-x)*(1-x)+(1-x)", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 12) pma = cmds.ls(type="plusMinusAverage") self.assertEqual(len(pma), 2)
def test_clamp(self): loc = cmds.spaceLocator()[0] result = dge("clamp(x, 0, 5)", x="{}.tx".format(loc)) cmds.connectAttr(result, "{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 6) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 5.0) cmds.setAttr("{}.tx".format(loc), -1) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 0.0) cmds.setAttr("{}.tx".format(loc), 1) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 1.0)
def __create_ik(self, ik_control, pole_vector, soft_ik_parent, global_scale_attr, scale_stretch): self.ik_handle = cmds.ikHandle( name="{}_ikh".format(self.name), solver="ikRPsolver", startJoint=self.start_joint, endEffector=self.end_joint, )[0] cmds.setAttr("{}.v".format(self.ik_handle), 0) # Drive visibility ik_vis = dge("1.0 - ikFk", ikFk="{}.ikFk".format(self.config_control)) for node in [ik_control, pole_vector]: vis = "{}.v".format(node) locked = cmds.getAttr(vis, lock=True) cmds.setAttr(vis, lock=False) cmds.connectAttr(ik_vis, "{}.v".format(node)) if locked: cmds.setAttr(vis, lock=True) dge( "ikBlend = 1.0 - ikFk", ikBlend="{}.ikBlend".format(self.ik_handle), ikFk="{}.ikFk".format(self.config_control), ) self.soft_ik = cmds.createNode("transform", name="{}_soft_ik".format(self.name)) common.snap_to_position(self.soft_ik, self.end_joint) cmds.parent(self.ik_handle, self.soft_ik) cmds.parent(self.soft_ik, soft_ik_parent) self.__create_stretch(ik_control, global_scale_attr, scale_stretch) cmds.parent(self.end_loc, soft_ik_parent) cmds.poleVectorConstraint(pole_vector, self.ik_handle)
def create(self, global_scale_attr=None): cmds.addAttr( self.end_control, ln="stretch", minValue=0.0, maxValue=1.0, defaultValue=0.0, keyable=True, ) self.spline_chain, original_chain = common.duplicate_chain( self.start_joint, self.end_joint, prefix="ikSpine_") # Create the spline ik self.ik_handle, self.effector, self.curve = cmds.ikHandle( name="{}_ikh".format(self.name), solver="ikSplineSolver", startJoint=self.spline_chain[0], endEffector=self.spline_chain[-1], parentCurve=False, simplifyCurve=False, ) self.effector = cmds.rename(self.effector, "{}_eff".format(self.name)) self.curve = cmds.rename(self.curve, "{}_crv".format(self.name)) # Create the joints to skin the curve curve_start_joint = cmds.duplicate(self.start_joint, parentOnly=True, name="{}CurveStart_jnt".format( self.name))[0] start_parent = cmds.listRelatives(self.start_control, parent=True, path=True) if start_parent: cmds.parent(curve_start_joint, start_parent[0]) common.opm_point_constraint(self.start_control, curve_start_joint) curve_end_joint = cmds.duplicate(self.end_joint, parentOnly=True, name="{}CurveEnd_jnt".format( self.name))[0] cmds.parent(curve_end_joint, self.end_control) for node in [curve_start_joint, curve_end_joint]: cmds.setAttr("{}.v".format(node), 0) # Skin curve cmds.skinCluster( curve_start_joint, curve_end_joint, self.curve, name="{}_scl".format(self.name), tsb=True, ) # Create stretch network curve_info = cmds.arclen(self.curve, constructionHistory=True) scale = dge( "lerp(1, arclength / (restLength * globalScale), stretch)", container="{}_stretch_scale".format(self.name), arclength="{}.arcLength".format(curve_info), restLength=cmds.getAttr("{}.arcLength".format(curve_info)), globalScale=global_scale_attr or 1.0, stretch="{}.stretch".format(self.end_control), ) # Connect to joints for joint in self.spline_chain[1:]: tx = cmds.getAttr("{}.translateX".format(joint)) mdl = cmds.createNode("multDoubleLinear", name="{}Stretch_mdl".format(joint)) cmds.setAttr("{}.input1".format(mdl), tx) cmds.connectAttr(scale, "{}.input2".format(mdl)) cmds.connectAttr("{}.output".format(mdl), "{}.translateX".format(joint)) joint_up = OpenMaya.MVector(0.0, 1.0, 0.0) start_joint_path = shortcuts.get_dag_path2(self.start_joint) start_control_path = shortcuts.get_dag_path2(self.start_control) up_vector_start = (joint_up * start_joint_path.inclusiveMatrix() * start_control_path.inclusiveMatrixInverse()) end_joint_path = shortcuts.get_dag_path2(self.end_joint) end_control_path = shortcuts.get_dag_path2(self.end_control) up_vector_end = (joint_up * end_joint_path.inclusiveMatrix() * end_control_path.inclusiveMatrixInverse()) # Setup advanced twist cmds.setAttr("{}.dTwistControlEnable".format(self.ik_handle), True) cmds.setAttr("{}.dWorldUpType".format(self.ik_handle), 4) # Object up cmds.setAttr("{}.dWorldUpAxis".format(self.ik_handle), 0) # Positive Y Up cmds.setAttr("{}.dWorldUpVectorX".format(self.ik_handle), up_vector_start.x) cmds.setAttr("{}.dWorldUpVectorY".format(self.ik_handle), up_vector_start.y) cmds.setAttr("{}.dWorldUpVectorZ".format(self.ik_handle), up_vector_start.z) cmds.setAttr("{}.dWorldUpVectorEndX".format(self.ik_handle), up_vector_end.x) cmds.setAttr("{}.dWorldUpVectorEndY".format(self.ik_handle), up_vector_end.y) cmds.setAttr("{}.dWorldUpVectorEndZ".format(self.ik_handle), up_vector_end.z) cmds.connectAttr( "{}.worldMatrix[0]".format(self.start_control), "{}.dWorldUpMatrix".format(self.ik_handle), ) cmds.connectAttr( "{}.worldMatrix[0]".format(self.end_control), "{}.dWorldUpMatrixEnd".format(self.ik_handle), ) # Constrain original chain back to spline chain for ik_joint, joint in zip(self.spline_chain, original_chain): if joint == self.end_joint: cmds.pointConstraint(ik_joint, joint, mo=True) cmds.orientConstraint(self.end_control, joint, mo=True) elif joint == self.start_joint: cmds.parentConstraint(self.start_control, joint, mo=True) else: cmds.parentConstraint(ik_joint, joint)
def __create_fk(self, parent): for name, joint in [ ("start_fk_control", self.start_joint), ("mid_fk_control", self.mid_joint), ("end_fk_control", self.end_joint), ]: control = cmds.createNode("transform", name="{}_fk_ctrl".format(joint)) common.snap_to(control, joint) common.lock_and_hide(control, "sv") setattr(self, name, control) if parent: cmds.parent(control, parent) common.freeze_to_parent_offset(control) parent = control ori = cmds.orientConstraint(control, joint)[0] cmds.connectAttr("{}.ikFk".format(self.config_control), "{}.{}W0".format(ori, control)) # Drive visibility visibility = "{}.v".format(control) locked = cmds.getAttr(visibility, lock=True) cmds.setAttr(visibility, lock=False) cmds.connectAttr("{}.ikFk".format(self.config_control), visibility) if locked: cmds.setAttr(visibility, lock=True) for joint, node in [ (self.start_joint, self.start_fk_control), (self.mid_joint, self.mid_fk_control), ]: cmds.addAttr(node, ln="length", minValue=0, defaultValue=1, keyable=True) scale = cmds.listConnections("{}.sx".format(joint), d=False, plugs=True)[0] dge( "sx = lerp(scale, length, ikFk)", sx="{}.sx".format(joint), scale=scale, length="{}.length".format(node), ikFk="{}.ikFk".format(self.config_control), ) for control in [self.mid_fk_control, self.end_fk_control]: parent_control = cmds.listRelatives(control, parent=True, path=True)[0] compose = cmds.createNode("composeMatrix") offset = common.local_offset(control) dge( "x = (length - 1.0) * tx", container="{}_length_offset".format(control), x="{}.inputTranslateX".format(compose), length="{}.length".format(parent_control), tx=offset.getElement(3, 0), ) mult = cmds.createNode("multMatrix") cmds.connectAttr("{}.outputMatrix".format(compose), "{}.matrixIn[0]".format(mult)) cmds.setAttr( "{}.matrixIn[1]".format(mult), cmds.getAttr("{}.offsetParentMatrix".format(control)), type="matrix", ) cmds.connectAttr( "{}.matrixSum".format(mult), "{}.offsetParentMatrix".format(control), )
def __create_stretch(self, ik_control, global_scale_attr=None, scale_stretch=True): """Create the stretchy soft ik setup. :param ik_control: Name of the node to use as the ik control. :param global_scale_attr: Optional attribute containing global scale value. :param scale_stretch: True to stretch with scale, False to use translate. """ for attr in ["stretch", "softIk"]: cmds.addAttr( ik_control, ln=attr, minValue=0.0, maxValue=1.0, defaultValue=0.0, keyable=True, ) # Locator for start distance measurement self.start_loc = cmds.spaceLocator( name="{}_stretch_start".format(self.name))[0] parent = cmds.listRelatives(self.start_joint, parent=True, path=True) if parent: cmds.connectAttr( "{}.worldMatrix[0]".format(parent[0]), "{}.offsetParentMatrix".format(self.start_loc), ) common.snap_to_position(self.start_loc, self.start_joint) # Locator for end distance measurement self.end_loc = cmds.spaceLocator( name="{}_stretch_end".format(self.name))[0] cmds.setAttr("{}.v".format(self.end_loc), 0) common.snap_to_position(self.end_loc, self.end_joint) rest_length = shortcuts.distance(self.start_joint, self.mid_joint) rest_length += shortcuts.distance(self.mid_joint, self.end_joint) length_ratio = dge( "distance(start, end) / (restLength * globalScale)", container="{}_percent_from_rest".format(self.name), start=self.start_loc, end=self.end_loc, restLength=rest_length, globalScale=global_scale_attr or 1.0, ) # Prevent divide by 0 softik = dge("max(x, 0.001)", x="{}.softIk".format(ik_control)) # We need to adjust offset the ik handle and scale the joints to create the soft # effect # See this graph to see the the softIk and scale values plotted # https://www.desmos.com/calculator/csi40rsztl # x = length_ratio # f(x) = softik_scale # c(x) = Scale x of the joints # s = softIk attribute # t = stretch attribute softik_scale = dge( "x > (1.0 - softIk)" "? (1.0 - softIk) + softIk * (1.0 - exp(-(x - (1.0 - softIk)) / softIk)) " ": x", container="{}_softik".format(self.name), x=length_ratio, softIk=softik, ) compose_matrix = cmds.createNode("composeMatrix") # Set the effector position dge( "tx = restLength * lerp(softIk, lengthRatio, stretch)", container="{}_effector_position".format(self.name), tx="{}.inputTranslate.inputTranslateX".format(compose_matrix), restLength=rest_length, lengthRatio=length_ratio, softIk=softik_scale, stretch="{}.stretch".format(ik_control), ) # Drive the joint scale for stretch scale = dge( "lerp(1, lengthRatio / softIk, stretch)", container="{}_stretch_scale".format(self.name), lengthRatio=length_ratio, softIk=softik_scale, stretch="{}.stretch".format(ik_control), ) if scale_stretch: for node in [self.start_joint, self.mid_joint]: cmds.connectAttr(scale, "{}.sx".format(node)) inverse_scale = dge("1/sx", sx="{}.sx".format(node)) cmds.connectAttr(inverse_scale, "{}.sy".format(node)) cmds.connectAttr(inverse_scale, "{}.sz".format(node)) else: for node in [self.mid_joint, self.end_joint]: tx = cmds.getAttr("{}.tx".format(node)) dge("x = {} * s".format(tx), x="{}.tx".format(node), s=scale) # Drive the soft ik transform aim = cmds.createNode("aimMatrix") cmds.connectAttr("{}.worldMatrix[0]".format(self.start_loc), "{}.inputMatrix".format(aim)) cmds.connectAttr( "{}.worldMatrix[0]".format(self.end_loc), "{}.primary.primaryTargetMatrix".format(aim), ) mult = cmds.createNode("multMatrix") cmds.connectAttr("{}.outputMatrix".format(compose_matrix), "{}.matrixIn[0]".format(mult)) cmds.connectAttr("{}.outputMatrix".format(aim), "{}.matrixIn[1]".format(mult)) parent = cmds.listRelatives(self.soft_ik, parent=True, path=True)[0] if parent: cmds.connectAttr("{}.worldInverseMatrix[0]".format(parent), "{}.matrixIn[2]".format(mult)) pick = cmds.createNode("pickMatrix") cmds.connectAttr("{}.matrixSum".format(mult), "{}.inputMatrix".format(pick)) for attr in ["Scale", "Shear", "Rotate"]: cmds.setAttr("{}.use{}".format(pick, attr), 0) cmds.connectAttr("{}.outputMatrix".format(pick), "{}.offsetParentMatrix".format(self.soft_ik)) cmds.setAttr("{}.t".format(self.soft_ik), 0, 0, 0)
def test_sqrt(self): loc = cmds.spaceLocator()[0] dge("y=sqrt(x)", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 10.2) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, math.sqrt(10.2), places=5)
def create_space_switch(node, drivers, switch_attribute=None, use_translate=True, use_rotate=True): """Creates a space switch network. The network uses the offsetParentMatrix attribute and does not create any constraints or new dag nodes. :param node: Transform to drive :param drivers: List of tuples: [(driver1, "spaceName1"), (driver2, "spaceName2")] :param switch_attribute: Name of the switch attribute to create on the target node. """ if switch_attribute is None: switch_attribute = "space" if cmds.objExists("{}.{}".format(node, switch_attribute)): cmds.deleteAttr(node, at=switch_attribute) names = [d[1] for d in drivers] cmds.addAttr(node, ln=switch_attribute, at="enum", en=":".join(names), keyable=True) # Create attribute to toggle translation in the matrices enable_translate_attr = _create_bool_attribute( node, "{}UseTranslate".format(switch_attribute), use_translate) # Create attribute to toggle rotation in the matrices enable_rotate_attr = _create_bool_attribute( node, "{}UseRotate".format(switch_attribute), use_rotate) blend = cmds.createNode("blendMatrix", name="{}_spaceswitch".format(node)) # Get the current offset parent matrix. This is used as the starting blend point m = OpenMaya.MMatrix(cmds.getAttr("{}.offsetParentMatrix".format(node))) cmds.setAttr("{}.inputMatrix".format(blend), list(m), type="matrix") parent = cmds.listRelatives(node, parent=True, path=True) to_parent_local = "{}.worldInverseMatrix[0]".format( parent[0]) if parent else None for i, driver in enumerate(drivers): driver = driver[0] _connect_driver_matrix_network(blend, node, driver, i, to_parent_local) target_attr = "{}.target[{}]".format(blend, i) # Hook up the weight toggle when switching spaces dge( "x = switch == {} ? 1 : 0".format(i), x="{}.weight".format(target_attr), switch="{}.{}".format(node, switch_attribute), ) # Connect the translation, rotation toggles cmds.connectAttr(enable_translate_attr, "{}.useTranslate".format(target_attr)) cmds.connectAttr(enable_rotate_attr, "{}.useRotate".format(target_attr, i)) cmds.connectAttr("{}.outputMatrix".format(blend), "{}.offsetParentMatrix".format(node))
def test_assignment(self): loc = cmds.spaceLocator()[0] result = dge("y=x^2", x="{}.tx".format(loc), y="{}.ty".format(loc)) cmds.setAttr("{}.tx".format(loc), 5) y = cmds.getAttr("{}.ty".format(loc)) self.assertAlmostEquals(y, 25)