Пример #1
0
def _set_attribute_anim_data(mplug, data):
    mcurve = omanim.MFnAnimCurve(mplug)
    try:
        mcurve.name()  #errors if does not exist
    except RuntimeError:
        mcurve.create(mplug)  #create one if didn't exist
    set_anim_curve_data(mcurve, data)
Пример #2
0
def api():
    """This function instead uses the OpenMaya api to query the keyframes"""
    # Get the start time
    start = time.time()
    # Get an iterator with all the anim curves in the scene
    # The argument provided is the type to search for
    it = om.MItDependencyNodes(om.MFn.kAnimCurveTimeToAngular)

    # This set will store all our keyframes
    keyframes = set()

    # We iterate through the iterator till it's done
    while not it.isDone():
        # We get the Function sets for the anim curves so we can interact with the curve
        curveFn = oma.MFnAnimCurve(it.thisNode())
        # We'll check how many keys it has
        for x in range(curveFn.numKeys):
            # Then query what time it happens on
            # The input method gives us back an MTime object
            # The value of the MTime is the frame number
            frame = curveFn.input(x).value

            # Then lets add it to the set
            keyframes.add(frame)

        # Finally go on to the next item in the iterator
        it.next()

    # Calculate how long this took and then return it
    delta = time.time() - start
    return delta
Пример #3
0
def add_keys(anim_curve, key_dict):
    # type: (unicode, dict) -> None
    """
    Add keyframes to animation curve

    :param anim_curve: animation curve name
    :param key_dict: dictionary of keyframes in {frame_number (float): value (float)} format
    :return: None
    """

    unit = om.MTime.uiUnit()
    nodeNattr = cmds.listConnections(anim_curve, d=True, s=False, p=True)[0]
    selList = om.MSelectionList()
    selList.add(nodeNattr)
    mplug = selList.getPlug(0)
    dArrTimes = om.MTimeArray()
    dArrVals = om.MDoubleArray()

    if 'rotate' in nodeNattr:
        for i in key_dict.keys():
            dArrTimes.append(om.MTime(float(i), unit))
            dArrVals.append(om.MAngle.uiToInternal(key_dict[i]))
    else:
        for i in key_dict.keys():
            dArrTimes.append(om.MTime(float(i), unit))
            dArrVals.append(key_dict[i])

    crvFnc = oma.MFnAnimCurve(mplug)
    crvFnc.addKeys(dArrTimes, dArrVals, crvFnc.kTangentAuto, crvFnc.kTangentAuto)
Пример #4
0
def fill_keys(name, attrName, KEYS, frameValues):
    currentObject = name
    currentAttribute = attrName
    if not cmds.objExists('%s.%s' % (currentObject, currentAttribute)):
        return

    animCurveType = 1

    # Anim Curve data
    timeList = KEYS  #objectAttriData['timeList']
    valueList = frameValues  #objectAttriData['valueList']

    # Convert current object and attribute to a new MPlug object
    mSelectionList = om.MSelectionList()
    mSelectionList.add('%s.%s' % (currentObject, currentAttribute))
    currentMPlug = mSelectionList.getPlug(0)

    connectedList = currentMPlug.connectedTo(1, 0)
    newAnimCurve = 1
    if connectedList:
        connectedNode = connectedList[0].node()

        if connectedNode.hasFn(om.MFn.kAnimCurve):
            mfnAnimCurve = oma.MFnAnimCurve(connectedNode)
            newAnimCurve = 0

    if newAnimCurve == 1:
        mfnAnimCurve = oma.MFnAnimCurve()
        mfnAnimCurve.create(currentMPlug, animCurveType)

    #mfnAnimCurve.setPreInfinityType(preInfinity)
    #mfnAnimCurve.setPostInfinityType(postInfinity)
    #mfnAnimCurve.setIsWeighted(weightedTangents)

    mTimeList = om.MTimeArray()
    mDoubleValueList = om.MDoubleArray()

    for keyIndex in range(len(timeList)):
        mTimeList.append(om.MTime(timeList[keyIndex], om.MTime.uiUnit()))
        mDoubleValueList.append(valueList[keyIndex])

    mfnAnimCurve.addKeys(mTimeList, mDoubleValueList, 0, 0, 1)
    #cmds.filterCurve( '%s_%s' % (currentObject, currentAttribute) )
    for keyIndex in range(len(timeList)):
        pass
Пример #5
0
def it_node_anim_curves_data(dg_name, mitsel):
    while not mitsel.isDone():
        mcurve = omanim.MFnAnimCurve(mitsel.getDependNode())
        try:
            curve_data = get_anim_curve_data(mcurve, dg_name)
        except exceptions.OutputError:
            msg = '{} Skipping curve data.'.format(sys.exc_info()[1])
            LOGGER.warning(msg)
        else:
            yield curve_data
        finally:
            mitsel.next()
Пример #6
0
def it_anim_curves_data(*dg_names):
    """
    Gather data from the specified animation curve nodes.

    Args:
        *dg_names (str): Animation node dg names to process.

    Returns:
        dict: The collected data of each curve.
    """
    omslist = om.MSelectionList()
    for i, c in enumerate(dg_names):
        omslist.add(c)
        mcurve = omanim.MFnAnimCurve(omslist.getDependNode(i))
        yield get_anim_curve_data(mcurve)
Пример #7
0
def maya_api_addkeys(plugName, times, values):
    sel = OpenMaya.MSelectionList()
    sel.add(plugName)
    plug = sel.getPlug(0)

    animFn = OpenMayaAnim.MFnAnimCurve()
    animFn.create(plug, OpenMayaAnim.MFnAnimCurve.kAnimCurveTL)

    timeArray = OpenMaya.MTimeArray()
    valueArray = OpenMaya.MDoubleArray()

    for i in range(len(times)):
        timeArray.append(OpenMaya.MTime(times[i], OpenMaya.MTime.kSeconds))
        valueArray.append(values[i])

    animFn.addKeys(timeArray, valueArray)
Пример #8
0
def node_names_to_mfn_anim_curves(nodes):
    """ Convert an anim curve name to a maya.OpenMayaAnim.MFnAnimCurve object
    :param node: str or list() of string
    :param types: str or tuple() of str. Type of animation curves animCurveTU,
    TT, TA, TL
    :rtype: maya.OpenMayaAnim.MFnAnimCurve
    """
    if isinstance(nodes, basestring):
        nodes = [nodes]

    sel = om2.MSelectionList()
    mfn_anim_curves = []
    for i, node in enumerate(nodes):
        sel.add(node)
        mfn_anim_curves.append(oma2.MFnAnimCurve(sel.getDependNode(i)))
    return mfn_anim_curves
Пример #9
0
 def setAnimCurves(self):
     """
     :return: None
     Sets the animCurves for the selection
     """
     self.curveList = []
     animCurves = aou.extractAnimCurves(aou.getAllLinkedObjs())
     cmds.select(animCurves)
     selectionList = om.MGlobal.getActiveSelectionList()
     if selectionList.length() > 0:
         kd = {}
         iterator = om.MItSelectionList(selectionList, om.MFn.kDagNode)
         while not iterator.isDone():
             # iterator.getDependNode() returns mobj. we're forcing a AnimCurve Obj
             curve = oma.MFnAnimCurve(iterator.getDependNode())
             kd[curve] = []
             for k in range(curve.numKeys):
                 kd[curve].append(KeyFrame(curve, k))
             self.keydict.update(kd)
             self.curveList.append(curve)
             iterator.next()
Пример #10
0
def get_animcurve(m_dag_node, attribute):
    """Get MFnAnimCurve for given object and attribute.

    Args:
        m_dag_node (OpenMaya.MFnDagNode): maya object to get curve from.
        attribute (str): name of attribute to get curve from.

    Returns:
        OpenMayaAnim.MFnAnimCurve:
            the animation curve for given object, attribute.
    """
    m_plug = m_dag_node.findPlug(attribute, False)
    m_source = m_plug.source()
    if m_source.isNull:
        return

    m_source_node = m_source.node()
    if not m_source_node.apiTypeStr.startswith('kAnimCurve'):
        return

    fn_curve = OpenMayaAnim.MFnAnimCurve(m_source_node)
    return fn_curve
Пример #11
0
    dep_data = OpenMaya.MFnDependencyNode(node_obj)
    
    #listing the plug connection
    plug = dep_data.findPlug(attr,0)
    nodes = plug.connectedTo(True, False)
    if not nodes:
        return    
    #if we have a connection we grab the first one and check if it is 
    #and animation curve
    node = nodes[0].node()
    if (node.hasFn(OpenMaya.MFn.kAnimCurve)):
        return  node


node =  __get_anim_crv(obj,attr)
anim =OpenMayaAnim.MFnAnimCurve(node)

x1 = 3.0
x2 = 3.002 
t = OpenMaya.MTime(x1)
t2 = OpenMaya.MTime(x2)
y1 = anim.evaluate(t)
y2 = anim.evaluate(t2)
#so we have a vector, namely v1= (x,y) , v2 = (x2,y2)
# we can extract the tangent vector by doing tanV = v2 -v1
print y2
time = OpenMaya.MTime(1.0,OpenMaya.MTime.kSeconds)
conversionFactor= time.asUnits(OpenMaya.MTime.uiUnit())
tanV = [x2-x1, y2-y1]
print tanV
Пример #12
0
 def create_ac(self, dag_fn, dag, attribute, curve_type):
     attr = dag_fn.attribute(attribute)
     anim_curve = oma.MFnAnimCurve()
     anim_curve.create(dag.transform(), attr, curve_type)
     return anim_curve
Пример #13
0
        mfnAttribute = om.MFnAttribute(attriMObject)
        attriName = mfnAttribute.name.encode()

        # 3. Find the Attribute - Animation curve is connected
        currentPlug = mfnDependencyNode.findPlug(attriName, 1)
        if currentPlug.connectedTo(1, 0):
            connectedList = currentPlug.connectedTo(1, 0)

            # Find the connected node type
            conNodeMObject = connectedList[0].node()

            # Check its is a Animation curve
            if conNodeMObject.hasFn(om.MFn.kAnimCurve):

                # Read Anim Curve Attribute valus
                mfnAnimCurve = oma.MFnAnimCurve(conNodeMObject)

                attributeDataList = {
                    'animCurveType': 0,
                    'preInfinity': 0,
                    'postInfinity': 0,
                    'weightedTangents': 0,
                    'time': [],
                    'value': [],
                    'inTangentType': [],
                    'inTangentAngle': [],
                    'inTangentWeight': [],
                    'outTangentType': [],
                    'outTangentAngle': [],
                    'outTangentWeight': []
                }
Пример #14
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)
Пример #15
0
def exportAnimCurve(selectionList,
                    animPath,
                    animName,
                    animGIFPath,
                    iconPath):

    animDataDict = {}
    for currentObject in selectionList:
        objMSL = om.MSelectionList()
        objMSL.add(currentObject)
        objMObject = objMSL.getDependNode(0)

        # find the attribute of current object
        objMFnDPNode = om.MFnDependencyNode(objMObject)
        attributeCount = objMFnDPNode.attributeCount()

        nodeAnimInformationDict = {}

        for attributeIndex in range(attributeCount):
            attributeMObject = objMFnDPNode.attribute(attributeIndex)

            MFnAttribute = om.MFnAttribute(attributeMObject)

            attributeName = MFnAttribute.name

            # find the attribute that anim curve connected

            currentPlug = objMFnDPNode.findPlug(attributeName, 1)

            if currentPlug.connectedTo(1, 0):
                currentConnectedList = currentPlug.connectedTo(1, 0)

                # find the connected node type
                currentConnectNodeMObject = currentConnectedList[0].node()

                # check it is an anim curve
                if currentConnectNodeMObject.hasFn(om.MFn.kAnimCurve):
                    # get anim curve
                    MFnAnimCurve = oma.MFnAnimCurve(currentConnectNodeMObject)

                    # get attribute
                    animCurveType = MFnAnimCurve.animCurveType
                    preInfinity = MFnAnimCurve.preInfinityType
                    postInfinity = MFnAnimCurve.postInfinityType

                    weightedTangents = int(MFnAnimCurve.isWeighted)

                    # get value of each key
                    numKeys = MFnAnimCurve.numKeys
                    timeList = []
                    valueList = []

                    inTangentTypeList = []
                    inTangentAngleList = []
                    inTangentAngleWeightList = []

                    outTangentTypeList = []
                    outTangentAngleList = []
                    outTangentAngleWeightList = []

                    for index in range(numKeys):
                        # time
                        input = MFnAnimCurve.input(index)
                        mTime = om.MTime(input)
                        currentTime = mTime.value
                        timeList.append(currentTime)

                        # value
                        value = MFnAnimCurve.value(index)
                        valueList.append(value)

                        # inTangent
                        inTangentType = MFnAnimCurve.inTangentType(index)
                        inTangentTypeList.append(inTangentType)

                        inTangentAngleWeight = MFnAnimCurve.getTangentAngleWeight(index, 1)

                        inTangentAngleMAngle = om.MAngle(inTangentAngleWeight[0])
                        inTangentValue = inTangentAngleMAngle.value
                        inTangentAngleList.append(inTangentValue)
                        inTangentAngleWeightList.append(inTangentAngleWeight[1])

                        # outTangent
                        outTangentType = MFnAnimCurve.outTangentType(index)
                        outTangentTypeList.append(outTangentType)

                        outTangentAngleWeight = MFnAnimCurve.getTangentAngleWeight(index, 0)
                        outTangentAngleMAngle = om.MAngle(outTangentAngleWeight[0])
                        outTangetValue = outTangentAngleMAngle.value
                        outTangentAngleList.append(outTangetValue)
                        outTangentAngleWeightList.append(outTangentAngleWeight[1])

                    attributeDataDict = {'animCurveType': animCurveType,
                                         'preInfinity': preInfinity,
                                         'postInfinity': postInfinity,
                                         'weightedTangents': weightedTangents,
                                         'numKeys': numKeys,
                                         'timeList': timeList,
                                         'valueList': valueList,
                                         'inTangentTypeList': inTangentTypeList,
                                         'inTangentAngleList': inTangentAngleList,
                                         'inTangentAngleWeightList': inTangentAngleWeightList,
                                         'outTangentAngleList': outTangentAngleList,
                                         'outTangentTypeList': outTangentTypeList,
                                         'outTangentAngleWeightList': outTangentAngleWeightList}

                    nodeAnimInformationDict.setdefault(attributeName, attributeDataDict)

        animDataDict.setdefault(currentObject.encode(), nodeAnimInformationDict)

    # Data History
    owner = os.getenv('USERNAME')
    time = datetime.datetime.now().strftime("%A, %B, %d, %Y %H:%M %p")
    mayaVersion = cmds.about(q=1, v=1)
    version = '0.1'
    dataList = {'animCurve': animDataDict, 'history': [owner, time, mayaVersion, version]}

    dataPath = '%s/%s.anim' % (animPath, animName)

    if os.path.isfile(dataPath):
        try:
            os.chmod(dataPath, 0777)
            os.remove(dataPath)
        except Exception, result:
            print result
Пример #16
0
def pasteAnimation():
    '''一時ファイルからアニメーションデータを読み込み、クリップボードに再登録
    する。その後、選択されたオブジェクトにアニメーションのペーストを試みる。
    '''

    # jsonデータの読み込み
    jsonPath = 'C:/Users/0300091280/Desktop/hoge.json'
    with open(jsonPath, 'r') as jsonFile:
        animData = json.load(jsonFile)

    clipboard = oma.MAnimCurveClipboard.theAPIClipboard
    clipboard.clear()
    clipItems = list()

    # progress bar
    gMainProgressBar = mm.eval('$tmp = $gMainProgressBar')
    mc.progressBar(gMainProgressBar,
                   edit=True,
                   beginProgress=True,
                   isInterruptable=False,
                   status='Import Animation Clipboard ...',
                   maxValue=len(animData['animData']))

    # アニメ情報を読み込んでMFnAnimCurveを作り、クリップボードに格納する。
    for curveData in animData['animData']:

        # カーブタイプを取り出し、MFnAnimCurveを作る。
        curveType = curveData['curveType']
        animCurve = oma.MFnAnimCurve()
        animCurveObj = animCurve.create(curveType)

        # カーブ固有の情報をセット
        animCurve.setIsWeighted(curveData['isWeight'])
        animCurve.setPreInfinityType(curveData['preInf'])
        animCurve.setPostInfinityType(curveData['postInf'])

        # 先に時間情報を30FPS換算に変換しておく
        convTime = [
            om.MTime(x, om.MTime.k30FPS) for x in curveData['keyData']['times']
        ]
        '''上でやってるのはこの処理
        convTime = list()
        for x in curveData['keyData']['times'] :
            mTime = om.MTime(x, om.MTime.k30FPS)
            convTime.append(mTime)
        
        速くするなら配列を作るときに先にサイズを確定させる
        convTime = [ None ] * len(curveData['keyData']['times'])
        for i in range(len(curveData['keyData']['times'])) :
            mTime = om.MTime(x, om.MTime.k30FPS)
            convTime[i] = mTime
        '''

        # キー情報を一気に流し込む
        animCurve.addKeysWithTangents(
            convTime,  #curveData['keyData']['times'],
            curveData['keyData']['values'],
            oma.MFnAnimCurve.kTangentAuto,
            oma.MFnAnimCurve.kTangentAuto,
            curveData['keyData']['inTangentType'],
            curveData['keyData']['outTangentType'],
            curveData['keyData']['inTangentX'],
            curveData['keyData']['inTangentY'],
            curveData['keyData']['outTangentX'],
            curveData['keyData']['outTangentY'],
            curveData['keyData']['tangentLock'],
            curveData['keyData']['weightLock'])

        # addKeysWithTangents()では個別のタンジェントタイプが反映されないので、
        # キー毎にタンジェントを設定。ついでにisBreakdownもセットする。
        for i in range(len(curveData['keyData']['times'])):
            inT = curveData['keyData']['inTangentType'][i]
            outT = curveData['keyData']['outTangentType'][i]
            breakdown = curveData['keyData']['breakdown'][i]

            animCurve.setInTangentType(i, inT)
            animCurve.setOutTangentType(i, outT)
            animCurve.setIsBreakdown(i, breakdown)

        # クリップボードアイテムに登録する
        clipboardItem = oma.MAnimCurveClipboardItem()
        clipboardItem.setAddressingInfo(curveData['addrInfo'][0],
                                        curveData['addrInfo'][1],
                                        curveData['addrInfo'][2])
        clipboardItem.setNameInfo(curveData['nodeName'], curveData['fullName'],
                                  curveData['leafName'])
        clipboardItem.setAnimCurve(animCurveObj)
        clipItems.append(clipboardItem)

        # update progress bar
        mc.progressBar(gMainProgressBar, edit=True, step=1)

    # クリップボードに登録
    clipboard.set(clipItems)

    # ペースト時にクリップボードに格納されているノード名と選択されているノード名
    # との文字列比較を行うので、まずクリップボードのノード名を取り出す。
    clipboardArray = clipboard.clipboardItems()
    clipboardItemNames = []
    for clipboardItem in clipboardArray:
        clipboardItemNames.append(clipboardItem.nodeName)

    # 順番はそのままで、重複したノード名を削除する
    clipboardItemNames = sorted(set(clipboardItemNames),
                                key=clipboardItemNames.index)
    '''
    print '---clipboard item list---'
    for i in clipboardItemNames: print i
    '''

    # クリップボードに含まれたノード名と選択されたノード名で文字列比較を行い、
    # 同じノード名があったら新しい選択リストに登録する。
    # これでクリップボードと同じ順番で再選択できる。
    selectedItems = mc.ls(selection=True)
    newSelectionList = []
    isNameMatched = False

    for cItem in clipboardItemNames:
        cItemSplit = re.split('[:\|]', cItem)
        for sItem in selectedItems:
            sItemSplit = re.split('[:\|]', sItem)
            if cItemSplit[-1] == sItemSplit[-1]:
                newSelectionList.append(sItem)
                isNameMatched = True
                break
    '''
    print '---newSelecton item list---'
    for i in newSelectionList: print i
    '''

    # マッチしたノード名があった場合、クリップボード順になったリストで選択しなおす。
    # その後、再選択したノード名とクリップボード内のノード名をもう一度比較し、
    # クリップボードにだけ存在するノード名の要素を削除する。
    # これをしないと順番が完全に合わない。めんどくさい。
    if isNameMatched:
        mc.select(clear=True)
        mc.select(newSelectionList)

        # 再選択したノード名のリスト化
        selectedItems = mc.ls(selection=True)
        sItemSplit = []
        for selectedItem in selectedItems:
            sItemSplit.append(re.split('[:\|]', selectedItem)[-1])

        # クリップボード内のノード名のリスト化
        cItemSplit = []
        for clipboardItem in clipboardArray:
            cNodeName = clipboardItem.nodeName
            cItemSplit.append(re.split('[:\|]', cNodeName)[-1])

        # 再選択したノード名がクリップボード内に存在するか調べ、
        # 存在している=消さないインデックスのリストを作る
        keepIndex = []
        for s in sItemSplit:
            for i, c in enumerate(cItemSplit):
                if s == c:
                    keepIndex.append(i)
        # print 'keepIndex', keepIndex

        # keepIndexの要素数とクリップボードの要素数を比較し、
        # 異なった場合は消す対象が存在するということになる。
        delIndex = list(range(len(cItemSplit)))

        if len(keepIndex) != len(delIndex):
            delIndex = list(set(delIndex) - set(keepIndex))
            # print 'deleteIndex', delIndex

            for i in reversed(delIndex):
                clipboardArray.remove(i)

        # debug クリップボードの中を再確認
        '''
        for clipboardItem in clipboardArray:
            animCurveObj = clipboardItem.animCurve
            animCurve = oma.MFnAnimCurve(animCurveObj)
            print clipboardItem.nodeName, clipboardItem.leafAttributeName, animCurve.value(0)
        print mc.ls(selection=True)
        '''

        # いじったclipboardArrayを再度クリップボードにセット
        clipboard.set(clipboardArray)

    # タイムスライダーで選択されている時間を取得
    aPlayBackSliderPython = mm.eval('$tmpVar=$gPlayBackSlider')
    rangeArray = mc.timeControl(aPlayBackSliderPython, q=True, rangeArray=True)

    # 選択されたノードに対してペーストを実行
    mc.pasteKey(clipboard='api',
                time=(rangeArray[0], rangeArray[0]),
                option='insert')

    # close progress bar
    mc.progressBar(gMainProgressBar, edit=True, endProgress=True)
Пример #17
0
def copyAnimation():
    '''選択されたオブジェクトのアニメーションをクリップボードにコピーし、
    一時ファイルに書き出す。
    時間の選択はタイムスライダーで行う。
    時間を選択していない場合はカレントフレームが対象。
    '''
    selectedItems = mc.ls(selection=True, long=True)

    # タイムスライダーで選択されている時間を取得
    aPlayBackSliderPython = mm.eval('$tmpVar=$gPlayBackSlider')
    rangeArray = mc.timeControl(aPlayBackSliderPython, q=True, rangeArray=True)

    # 選択されているオブジェクトのアニメーションをクリップボードにコピー
    mc.copyKey(selectedItems,
               clipboard='api',
               animation='objects',
               includeUpperBound=True,
               forceIndependentEulerAngles=True,
               time=(int(rangeArray[0]), int(rangeArray[1])),
               option='keys',
               shape=False)

    # クリップボードからアニメーションを取り出す
    clipboard = oma.MAnimCurveClipboard.theAPIClipboard
    clipboardArray = clipboard.clipboardItems()

    animData = dict()
    animData['animData'] = []

    # progress bar
    gMainProgressBar = mm.eval('$tmp = $gMainProgressBar')
    mc.progressBar(gMainProgressBar,
                   edit=True,
                   beginProgress=True,
                   isInterruptable=False,
                   status='Export Animation Clipboard ...',
                   maxValue=len(clipboardArray))

    # アニメカーブに1つずつアクセス
    for clipboardItem in clipboardArray:
        animCurveObj = clipboardItem.animCurve
        animCurve = oma.MFnAnimCurve(animCurveObj)

        try:
            animCurve.animCurveType  # アニメカーブがあるかチェック
        except:
            pass
            #print '{0} has no animation curve.'.format(clipboardItem.nodeName)
        else:
            # クリップボード再登録時に必要な情報と、アニメカーブ固有の情報を取得。
            addr = clipboardItem.getAddressingInfo()
            animData['animData'].append({
                'addrInfo': [addr[0], addr[1], addr[2]],
                'nodeName': clipboardItem.nodeName,
                'fullName': clipboardItem.fullAttributeName,
                'leafName': clipboardItem.leafAttributeName,
                'curveType': animCurve.animCurveType,
                'isWeight': animCurve.isWeighted,
                'preInf': animCurve.preInfinityType,
                'postInf': animCurve.postInfinityType,
                'keyData': {}
            })

            # キー情報の取り出し
            d = {
                'times': [],
                'values': [],
                'inTangentType': [],
                'outTangentType': [],
                'inTangentX': [],
                'inTangentY': [],
                'outTangentX': [],
                'outTangentY': [],
                'tangentLock': [],
                'weightLock': [],
                'breakdown': []
            }

            for j in range(animCurve.numKeys):
                d['times'].append(animCurve.input(j).asUnits(om.MTime.k30FPS))
                d['values'].append(animCurve.value(j))
                d['inTangentType'].append(animCurve.inTangentType(j))
                d['outTangentType'].append(animCurve.outTangentType(j))
                # animCurve.getTangentXY() is not working...
                ret = animCurve.getTangentAngleWeight(j, True)
                d['inTangentX'].append(3 * ret[1] * math.cos(ret[0].value))
                d['inTangentY'].append(3 * ret[1] * math.sin(ret[0].value))
                ret = animCurve.getTangentAngleWeight(j, False)
                d['outTangentX'].append(3 * ret[1] * math.cos(ret[0].value))
                d['outTangentY'].append(3 * ret[1] * math.sin(ret[0].value))
                d['tangentLock'].append(animCurve.tangentsLocked(j))
                d['weightLock'].append(animCurve.weightsLocked(j))
                d['breakdown'].append(animCurve.isBreakdown(j))

            animData['animData'][-1]['keyData'] = d

        # update progress bar
        mc.progressBar(gMainProgressBar, edit=True, step=1)

    # jsonデータとして書き出し
    jsonPath = 'C:/Users/0300091280/Desktop/hoge.json'
    with open(jsonPath, 'w') as jsonFile:
        json.dump(animData, jsonFile)

    # close progress bar
    mc.progressBar(gMainProgressBar, edit=True, endProgress=True)
Пример #18
0
def importAnimCurve(
    selectionList,
    animPath,
):

    animData = open(animPath, 'r')
    data = json.load(animData)
    animData.close()

    if data and selectionList:
        for eachObject in selectionList:
            for eachAnimCurveAttribute in data['animCurve'][eachObject]:

                # get value
                animCurveType = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['animCurveType']
                preInfinity = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['preInfinity']
                postInfinity = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['postInfinity']
                weightedTangents = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['weightedTangents']
                numKeys = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['numKeys']
                timeList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['timeList']
                valueList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['valueList']
                inTangentTypeList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['inTangentTypeList']
                inTangentAngleList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['inTangentAngleList']
                inTangentAngleWeightList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['inTangentAngleWeightList']
                outTangentTypeList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['outTangentTypeList']
                outTangentAngleList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['outTangentAngleList']
                outTangentAngleWeightList = data['animCurve'][eachObject][
                    eachAnimCurveAttribute]['outTangentAngleWeightList']

                # convert current object and attribute to a new MPlug object
                mSelectionList = om.MSelectionList()
                mSelectionList.add('%s.%s' %
                                   (eachObject, eachAnimCurveAttribute))

                attributeMPlug = mSelectionList.getPlug(0)

                connectedList = attributeMPlug.connectedTo(1, 0)

                # whether to create a new curve or use the existed curve
                newAnimCurve = 1
                if connectedList:
                    connectedNode = connectedList[0].node()

                    if connectedNode.hasFn(om.MFn.kAnimCurve):

                        MFnAnimCurve = oma.MFnAnimCurve(connectedNode)
                        newAnimCurve = 0

                if newAnimCurve:
                    MFnAnimCurve = oma.MFnAnimCurve()

                    MFnAnimCurve.create(attributeMPlug, animCurveType)

                # set value
                MFnAnimCurve.setPreInfinityType(preInfinity)
                MFnAnimCurve.setPostInfinityType(postInfinity)

                MFnAnimCurve.setIsWeighted(weightedTangents)

                MTimeArray = om.MTimeArray()
                MDoubleValueList = om.MDoubleArray()

                for index in range(len(timeList)):
                    MTimeArray.append(
                        om.MTime(timeList[index], om.MTime.uiUnit()))
                    MDoubleValueList.append(valueList[index])

                MFnAnimCurve.addKeys(MTimeArray, MDoubleValueList, 0, 0, 1)

                for index in range(len(timeList)):
                    MFnAnimCurve.setInTangentType(index,
                                                  inTangentTypeList[index])
                    MFnAnimCurve.setOutTangentType(index,
                                                   outTangentTypeList[index])

                    inTangentAngle = om.MAngle(inTangentAngleList[index])
                    outTangentAngle = om.MAngle(outTangentAngleList[index])

                    MFnAnimCurve.setAngle(index, inTangentAngle, 1)
                    MFnAnimCurve.setAngle(index, outTangentAngle, 0)

                    MFnAnimCurve.setWeight(index,
                                           inTangentAngleWeightList[index], 1)
                    MFnAnimCurve.setWeight(index,
                                           outTangentAngleWeightList[index], 0)

    # read history
    historyData = data['history']
    historyList = [
        'Owner: %s' % historyData[0],
        'Created: %s' % historyData[1],
        'Maya version: %s' % historyData[2],
        'Module version: %s' % historyData[3]
    ]

    return historyList
Пример #19
0
                # convert current object and attribute to a new MPlug object
                mSelectionList = om.MSelectionList()
                mSelectionList.add(eachAnimCurveAttribute)

                attributeMPlug = mSelectionList.getPlug(0)

                connectedList = attributeMPlug.connectedTo(1, 0)

                # whether to create a new curve or use the existed curve
                newAnimCurve = 1
                if connectedList:
                    connectedNode = connectedList[0].node()

                    if connectedNode.hasFn(om.MFn.kAnimCurve):

                        MFnAnimCurve = oma.MFnAnimCurve(connectedNode)
                        newAnimCurve = 0

                if newAnimCurve:
                    MFnAnimCurve = oma.MFnAnimCurve()

                    MFnAnimCurve.create(attributeMPlug, animCurveType)

                # set value
                MFnAnimCurve.setPreInfinityType(preInfinity)
                MFnAnimCurve.setPostInfinityType(postInfinity)

                MFnAnimCurve.setIsWeighted(weightedTangents)

                MTimeArray = om.MTimeArray()
                MDoubleValueList = om.MDoubleArray()
Пример #20
0
 def createAnimCurve(self,dagNodeFn,dag,attribute):
     attr = dagNodeFn.attribute(attribute)
     attrCurve = oma.MFnAnimCurve()
     attrCurve.create(dag.transform(),attr, None )
     
     return attrCurve
Пример #21
0
def prepare(mode):
    """
    Prepares the dictionary of animation curves along with values before/after required to interpolate.
    """
    global anim_cache
    global curve_key_values

    # get curves

    if utils.is_graph_editor():
        curves = utils.get_selected_anim_curves()
        plugs = None
    else:
        nodes = utils.get_selected_objects()
        curves, plugs = utils.get_anim_curves_from_objects(nodes)

    # get prev and next values so we can use them to blend while dragging slider
    is_default = bool(mode == options.BlendingMode.default)
    is_curve_tangent = bool(mode == options.BlendingMode.curve)

    curve_key_values = {}
    time_range = utils.get_time_slider_range()
    unit = om.MTime.uiUnit()
    mtime_range = (om.MTime(time_range[0],
                            unit), om.MTime(time_range[1], unit))

    for plug_idx, curve_node in enumerate(curves):
        curve_fn = oma.MFnAnimCurve(curve_node.object())

        default_val = None
        if is_default:
            if plugs:
                default_val = utils.get_attribute_default_value(
                    plugs[plug_idx])
            else:
                default_val = utils.get_anim_curve_default_value(curve_fn)

        key_group = KeyGroup(key_index=[],
                             value=[],
                             default_value=default_val,
                             prev_value=[],
                             next_value=[],
                             tangent_points=[],
                             has_two_segments=[])

        selected_keys = cmds.keyframe(str(curve_fn.absoluteName()),
                                      q=True,
                                      selected=True,
                                      indexValue=True)
        if time_range[0] - time_range[1] != 0:
            # time range selected on time slider
            indices = cmds.keyframe(str(curve_fn.name()),
                                    q=True,
                                    time=time_range,
                                    iv=True)
            if indices is None:
                continue

            if indices[0] == 0:
                prev_index = 0
            else:
                prev_index = indices[0] - 1

            num_keys = curve_fn.numKeys
            next_index = indices[-1] + 1
            if next_index >= num_keys:
                next_index = num_keys - 1

            for idx in indices:
                add_to_key_group(curve_fn, idx, prev_index, next_index,
                                 key_group)

            if is_curve_tangent:
                for idx in indices:
                    add_tangent_points_to_key_group(key_group, curve_fn,
                                                    prev_index, next_index,
                                                    idx)

            curve_key_values[curve_fn] = key_group

        elif selected_keys is not None:
            # keys selected in graph editor or dope sheet
            index_group = []
            groups = []

            # find groups of consecutive key indices
            index_group.append(selected_keys[0])
            for i in range(1, len(selected_keys)):
                if selected_keys[i] - selected_keys[i - 1] < 2:
                    index_group.append(selected_keys[i])
                else:
                    groups.append(index_group)
                    index_group = [selected_keys[i]]

            # append last iteration
            groups.append(index_group)

            for grp in groups:
                prev_index = max(0, grp[0] - 1)
                next_index = min(grp[-1] + 1, curve_fn.numKeys - 1)

                for idx in grp:
                    add_to_key_group(curve_fn, idx, prev_index, next_index,
                                     key_group)

                if is_curve_tangent:
                    for idx in grp:
                        add_tangent_points_to_key_group(key_group,
                                                        curve_fn,
                                                        prev_index,
                                                        next_index,
                                                        index=idx)

            curve_key_values[curve_fn] = key_group
        else:
            # no time range or keys selected
            current_index = curve_fn.find(mtime_range[0])
            closest_index = curve_fn.findClosest(mtime_range[0])
            closest_time = curve_fn.input(closest_index)
            if current_index is not None:
                prev_index = max(0, closest_index - 1)
                next_index = min(curve_fn.numKeys - 1, closest_index + 1)

                # key exists, so two curve tangent segments
                if is_curve_tangent:
                    add_tangent_points_to_key_group(key_group, curve_fn,
                                                    prev_index, next_index,
                                                    current_index)
            else:
                if (closest_time.value - mtime_range[0].value) <= 0:
                    prev_index = closest_index
                    next_index = closest_index + 1
                else:
                    prev_index = closest_index - 1
                    next_index = closest_index

                if prev_index < 0:
                    prev_index = 0

                # add new key
                value = curve_fn.evaluate(mtime_range[0])
                current_index = curve_fn.addKey(mtime_range[0],
                                                value,
                                                change=anim_cache)

                next_index = min(curve_fn.numKeys - 1, next_index + 1)

                # there isn't any key yet, so we only have one tangent segment and thus index=None
                if is_curve_tangent:
                    add_tangent_points_to_key_group(key_group,
                                                    curve_fn,
                                                    prev_index,
                                                    next_index,
                                                    index=current_index)

            add_to_key_group(curve_fn, current_index, prev_index, next_index,
                             key_group)
            curve_key_values[curve_fn] = key_group
Пример #22
0
            attributeName = MFnAttribute.name

            # find the attribute that anim curve connected

            currentPlug = objMFnDPNode.findPlug(attributeName, 1)

            if currentPlug.connectedTo(1, 0):
                currentConnectedList = currentPlug.connectedTo(1, 0)

                # find the connected node type
                currentConnectNodeMObject = currentConnectedList[0].node()

                # check it is an anim curve
                if currentConnectNodeMObject.hasFn(om.MFn.kAnimCurve):
                    # get anim curve
                    MFnAnimCurve = oma.MFnAnimCurve(currentConnectNodeMObject)

                    # get attribute
                    animCurveType = MFnAnimCurve.animCurveType
                    preInfinity = MFnAnimCurve.preInfinityType
                    postInfinity = MFnAnimCurve.postInfinityType

                    weightedTangents = int(MFnAnimCurve.isWeighted)

                    # get value of each key
                    numKeys = MFnAnimCurve.numKeys
                    timeList = []
                    valueList = []

                    inTangentTypeList = []
                    inTangentAngleList = []
Пример #23
0
def do():
    """
    Creates a key on all attributes at any time-value, where a key exists in the curves list
    :return True on complete, False if cancelled
    :rtype bool
    """
    # get selection
    if utils.is_graph_editor():
        curves = utils.get_selected_anim_curves()
    else:
        nodes = utils.get_selected_objects()
        curves, plugs = utils.get_anim_curves_from_objects(nodes)

    # get curve functions
    curve_fns = []
    for curve_node in curves:
        curve_fns.append(oma.MFnAnimCurve(curve_node.object()))

    if len(curve_fns) == 0:
        sys.stdout.write('# No anim curves to set keys on\n')
        return True

    # get time range
    time_range = utils.get_time_slider_range()
    is_range = time_range[0] - time_range[1] != 0

    # get time for keyframes
    times = set()
    selected_keys = cmds.keyframe(
        q=True, selected=True, timeChange=True) if is_range is False else None

    if is_range:
        unit = om.MTime.uiUnit()
        min_time = om.MTime(time_range[0], unit)
        max_time = om.MTime(time_range[1], unit)
        for curve_fn in curve_fns:
            start_index = max(0, curve_fn.findClosest(
                min_time))  # -1 just to be safe, is checked later
            end_index = min(
                curve_fn.numKeys,
                curve_fn.findClosest(max_time))  # +1 just to be safe
            for i in range(start_index, end_index):
                times.add(curve_fn.input(i).value)
    elif selected_keys is not None:
        times = set(selected_keys)
    else:
        for curve_fn in curve_fns:
            for i in range(curve_fn.numKeys):
                times.add(curve_fn.input(i).value)

    # get main progress bar start progress
    gMainProgressBar = mel.eval('$tmp = $gMainProgressBar')
    cmds.progressBar(gMainProgressBar,
                     e=True,
                     beginProgress=True,
                     isInterruptable=True,
                     status='Adding keyframes...',
                     maxValue=len(curve_fns))

    # convert to MTime()
    m_times = []
    unit = om.MTime.uiUnit()
    if is_range:
        for t in times:
            if time_range[0] <= t <= time_range[1]:
                m_times.append(om.MTime(t, unit))
    else:
        for t in times:
            m_times.append(om.MTime(t, unit))

    # add keys
    key_count = 0
    cancelled = False
    for curve_fn in curve_fns:
        ts = []
        vs = []
        for mt in m_times:
            if curve_fn.find(mt) is None:
                ts.append(mt)
                vs.append(curve_fn.evaluate(mt))

        for t, v in zip(ts, vs):
            curve_fn.addKey(t, v, change=animdata.anim_cache)
            key_count += 1

        cmds.progressBar(gMainProgressBar, e=True, step=1)

        if cmds.progressBar(gMainProgressBar, q=True, isCancelled=True):
            cancelled = True
            break

    cmds.progressBar(gMainProgressBar, e=True, endProgress=True)

    if cancelled:
        sys.stdout.write('# Keyhammer cancelled...\n')
        return False
    else:
        sys.stdout.write('# Added %d key%s\n' %
                         (key_count, '' if key_count == 1 else 's'))

    return True