diff --git a/resources.qrc b/resources.qrc index ca54f89..30f68a3 100644 --- a/resources.qrc +++ b/resources.qrc @@ -23,5 +23,7 @@ resources/createHIKForActor.mel resources/setOrCreateRSIdAttribute.mel resources/deleteMappingAttribute.mel + resources/createFaceAttributes.mel + resources/fetchBlendShapes.mel diff --git a/resources/createFaceAttributes.mel b/resources/createFaceAttributes.mel new file mode 100644 index 0000000..2f118a4 --- /dev/null +++ b/resources/createFaceAttributes.mel @@ -0,0 +1,87 @@ +string $bsHostNode = "MAYA_OBJECT_NAME"; + + +proc _createFaceAttrs(string $bsNode) { + if(!`objExists $bsNode`) { + print("Node not found" + $bsNode + "\n"); + return; + } + + string $faceMappingName = "FACE_MAPPING_FILED_NAME"; + int $faceAttrExists = `attributeExists $faceMappingName $bsNode`; + + if ($faceAttrExists) { + // already been created + print("Face mapping already been created!\n"); + return; + } else { + // create compound attribute with 53 child attributes + + addAttr -longName $faceMappingName -numberOfChildren 53 -attributeType compound $bsNode; + addAttr -dt "string" -longName "FaceId" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeBlinkLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookDownLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookInLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookOutLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookUpLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeSquintLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeWideLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeBlinkRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookDownRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookInRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookOutRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeLookUpRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeSquintRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "eyeWideRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "jawForward" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "jawLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "jawRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "jawOpen" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthClose" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthFunnel" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthPucker" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthSmileLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthSmileRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthFrownLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthFrownRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthDimpleLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthDimpleRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthStretchLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthStretchRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthRollLower" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthRollUpper" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthShrugLower" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthShrugUpper" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthPressLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthPressRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthLowerDownLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthLowerDownRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthUpperUpLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "mouthUpperUpRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "browDownLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "browDownRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "browInnerUp" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "browOuterUpLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "browOuterUpRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "cheekPuff" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "cheekSquintLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "cheekSquintRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "noseSneerLeft" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "noseSneerRight" -parent $faceMappingName $bsNode; + addAttr -dt "string" -longName "tongueOut" -parent $faceMappingName $bsNode; + } +} + +proc createFaceMappingOnNode( string $targetNode ) { + string $connections[] = `listHistory -bf $targetNode`; + string $bsNodes[] = `ls -type "blendShape" $connections`; + + for ($bsNode in $bsNodes) { + // create face attributes for each BS node + _createFaceAttrs($bsNode); + } +} + +createFaceMappingOnNode($bsHostNode); diff --git a/resources/fetchBlendShapes.mel b/resources/fetchBlendShapes.mel new file mode 100644 index 0000000..18daba8 --- /dev/null +++ b/resources/fetchBlendShapes.mel @@ -0,0 +1,10 @@ +proc string getConnectedBlendShapes(string $targetNode, string $separator) { + if(!`objExists $targetNode`) { + return ""; + } + string $connections[] = `listHistory -bf $targetNode`; + string $bsNodes[] = `ls -type "blendShape" $connections`; + + return stringArrayToString($bsNodes, $separator); +} +getConnectedBlendShapes("MAYA_OBJECT_NAME", "SEPARATOR"); diff --git a/rslm.pro b/rslm.pro index 6b7e0fd..7f64527 100644 --- a/rslm.pro +++ b/rslm.pro @@ -32,6 +32,7 @@ SOURCES += \ src/main.cpp \ src/mapping.cpp \ src/receiverworker.cpp \ + src/recorder.cpp \ src/ui/button.cpp \ src/ui/categoryheader.cpp \ src/ui/commandapicontent.cpp \ @@ -47,6 +48,7 @@ HEADERS += \ src/constants.h \ src/mapping.h \ src/receiverworker.h \ + src/recorder.h \ src/singleton.h \ src/ui/button.h \ src/ui/categoryheader.h \ diff --git a/src/animations.cpp b/src/animations.cpp index 3c73777..8498e2d 100644 --- a/src/animations.cpp +++ b/src/animations.cpp @@ -1,6 +1,7 @@ #include #include "animations.h" +#include "recorder.h" #include "utils.h" #include "mapping.h" @@ -8,16 +9,17 @@ #include #include +#include +#include +#include #include #include +#include #include #include +#include #include -#ifdef _WINDOWS - #pragma comment(lib,"OpenMayaAnim.lib") -#endif - _Animations::_Animations() { @@ -70,59 +72,78 @@ void _Animations::applyAnimationsToMappedObjects() const QHash mayaToRsBoneNames = Mapping::get()->getBoneMapping(); const QHash studioTPose = Mapping::get()->getStudioTPose(); - struct Local { - static void animatePropOrTracker(QJsonObject obj, MDagPath dagPath) { -// bool isLive = obj["isLive"].toBool(); - - // do nothing is not live -// if(!isLive) return; - - QJsonObject postitionObject = obj["position"].toObject(); - QJsonObject rotationObject = obj["rotation"].toObject(); - MVector rsPosition(postitionObject["x"].toDouble(), - postitionObject["y"].toDouble(), - postitionObject["z"].toDouble()); - - MQuaternion rsRotation(rotationObject["x"].toDouble(), - rotationObject["y"].toDouble(), - rotationObject["z"].toDouble(), - rotationObject["w"].toDouble()); - - // convert RS transform to Maya transform - MVector mayaPosition = Utils::rsToMaya(rsPosition) * Animations::get()->sceneScale(); - MQuaternion mayaRotation = Utils::rsToMaya(rsRotation); - MTransformationMatrix finalTransform(MTransformationMatrix::identity); - finalTransform.setTranslation(mayaPosition, MSpace::kWorld); - finalTransform.setRotationQuaternion(mayaRotation.x, mayaRotation.y, mayaRotation.z, mayaRotation.w); - - // apply converted transform onto mapped object - MFnTransform fn(dagPath); - fn.set(finalTransform); - } - }; - QList allIds = objectMapping.keys(); for(QString rsId : allIds) { auto it = objectMapping.find(rsId); +// std::cout << "cnt: " << objectMapping.count(rsId) << std::endl; if(it != objectMapping.end()) { - while(it != objectMapping.end()) { + while(it != objectMapping.end()) + { QString rsId = it.key(); + + if(rsId.isEmpty()) { + ++it; + continue; + } + MObject object = it.value(); MDagPath dagPath; MDagPath::getAPathTo(object, dagPath); - // apply props animations if(propsMap.contains(rsId)) { + // apply props animations + QJsonObject propObject = propsMap[rsId]; - Local::animatePropOrTracker(propObject, dagPath); - // apply trackers animations + animatePropOrTracker(propObject, dagPath); +// printf("prop animated %s - %s\n", dagPath.partialPathName().asChar(), rsId.toStdString().c_str()); + } else if(trackersMap.contains(rsId)) { + + // apply trackers animations QJsonObject trackerObject = trackersMap[rsId]; - Local::animatePropOrTracker(trackerObject, dagPath); - // apply faces animations + animatePropOrTracker(trackerObject, dagPath); + } else if(facesMap.contains(rsId)) { - // apply actor animations + // apply face animations + + const QStringList faceShapeNames = Mapping::get()->getFaceShapeNames(); + // get input data for this face + QJsonObject faceObject = facesMap[rsId]; + + // access mapped blendshape node + MFnBlendShapeDeformer faceFn(object); + + // TODO: run this once when receiver stared + // prepare weight weight name to attribute plug map + QHash weightsMap; + Utils::fillFaceWeightsMap(faceFn, weightsMap); + + // for each studio shape name set weight value + for(QString studioShapeName : faceShapeNames) { + double weight = faceObject[studioShapeName].toDouble() * 0.01; + + MString bsFieldName = BLEND_SHAPE_PREFIX + studioShapeName.toStdString().c_str(); + MStatus fieldPlugStatus; + MPlug weightAttr = faceFn.findPlug(bsFieldName, true, &fieldPlugStatus); + if(fieldPlugStatus == MStatus::kSuccess) { + MString mappedName; + weightAttr.getValue(mappedName); + if(weightsMap.contains(mappedName.asChar())) { + MPlug weightPlug = weightsMap[mappedName.asChar()]; + weightPlug.setDouble(weight); + if(recordingEnabled) { + + // this functions will be called later + Recorder::get()->recordFace(timestamp, [object, weight, weightPlug](int frame) { + Recorder::get()->keyframeNumericAttribute(frame, weightPlug, weight); + }); + } + } + } + } + } else if(actorsMap.contains(rsId)) { + // apply actor animations QJsonObject actorObject = actorsMap[rsId]; // Hips joint mapped implicitly for user @@ -131,7 +152,7 @@ void _Animations::applyAnimationsToMappedObjects() // We transform HIK source skeleton. To see results on custom character user should create // Another character and define it, than set it as target for our generated character. - MItDag it; + MItDag jIt; MFnDagNode hipDagNode(dagPath); MVector referenceOffset; MQuaternion referenceQuat; @@ -145,10 +166,10 @@ void _Animations::applyAnimationsToMappedObjects() referenceQuat.normalizeIt(); } - it.reset(dagPath, MItDag::kBreadthFirst, MFn::kJoint); - while(!it.isDone()) { + jIt.reset(dagPath, MItDag::kBreadthFirst, MFn::kJoint); + while(!jIt.isDone()) { MDagPath jointPath; - it.getPath(jointPath); + jIt.getPath(jointPath); QString jointPathString(jointPath.fullPathName().asChar()); // this name contains character name CHARNAME_BONENAME @@ -188,16 +209,34 @@ void _Animations::applyAnimationsToMappedObjects() boneQuat *= referenceQuat; boneQuat.normalizeIt(); fnTr.setRotation(boneQuat, MSpace::kWorld); + + if(recordingEnabled) { + MVector recordLocation = fnTr.getTranslation(MSpace::kTransform); + MQuaternion recordRotation; + fnTr.getRotation(recordRotation); + Recorder::get()->recordBone(timestamp, [recordLocation, jointPath, recordRotation](int frame) { + Recorder::get()->keyframeNumericAttribute("tx", frame, jointPath, recordLocation.x); + Recorder::get()->keyframeNumericAttribute("ty", frame, jointPath, recordLocation.y); + Recorder::get()->keyframeNumericAttribute("tz", frame, jointPath, recordLocation.z); + + MEulerRotation euAngle = recordRotation.asEulerRotation(); + Recorder::get()->keyframeNumericAttribute("rx", frame, jointPath, euAngle.x); + Recorder::get()->keyframeNumericAttribute("ry", frame, jointPath, euAngle.y); + Recorder::get()->keyframeNumericAttribute("rz", frame, jointPath, euAngle.z); + }); + } } - it.next(); + jIt.next(); } } else { - // this should never happen - std::cout << "MAPPED OBJECTS NOT FOUND IN DATA STREAM!!!!\n"; - } + // this block executes only if rsId is not found in mapped objects + // for example user opened maya scene that doesn't corresponds to opened studio project + // TODO: tell user about problematic objects +// std::cout << "MAPPED OBJECTS NOT FOUND IN DATA STREAM!!!!\n"; + } ++it; } @@ -205,7 +244,66 @@ void _Animations::applyAnimationsToMappedObjects() } } +void _Animations::recordingToggled(bool enabled) +{ + recordingEnabled = enabled; + Recorder::get()->recordingToggled(enabled); + if(!recordingEnabled) { + // put cached data into timeline + Recorder::get()->finalizeRecording(); + } + +} + void _Animations::setSceneScale(float scale) { _sceneScale = scale; } + +void _Animations::animatePropOrTracker(QJsonObject obj, const MDagPath &dagPath) +{ +// bool isLive = obj["isLive"].toBool(); + + // do nothing if not live +// if(!isLive) return; + + QJsonObject postitionObject = obj["position"].toObject(); + QJsonObject rotationObject = obj["rotation"].toObject(); + MVector rsPosition(postitionObject["x"].toDouble(), + postitionObject["y"].toDouble(), + postitionObject["z"].toDouble()); + + MQuaternion rsRotation(rotationObject["x"].toDouble(), + rotationObject["y"].toDouble(), + rotationObject["z"].toDouble(), + rotationObject["w"].toDouble()); + + // convert RS transform to Maya transform + MVector mayaPosition = Utils::rsToMaya(rsPosition) * Animations::get()->sceneScale(); + MQuaternion mayaRotation = Utils::rsToMaya(rsRotation); + MTransformationMatrix finalTransform(MTransformationMatrix::identity); + finalTransform.setTranslation(mayaPosition, MSpace::kWorld); + finalTransform.setRotationQuaternion(mayaRotation.x, mayaRotation.y, mayaRotation.z, mayaRotation.w); + + // apply converted transform onto mapped object + MFnTransform fn(dagPath); + fn.set(finalTransform); + + if(recordingEnabled) { + Recorder::get()->recordPropOrTracker(timestamp, [mayaPosition, mayaRotation, dagPath](int frame){ + Recorder::get()->keyframeNumericAttribute("tx", frame, dagPath, mayaPosition.x); + Recorder::get()->keyframeNumericAttribute("ty", frame, dagPath, mayaPosition.y); + Recorder::get()->keyframeNumericAttribute("tz", frame, dagPath, mayaPosition.z); + + MEulerRotation euAngle = mayaRotation.asEulerRotation(); + Recorder::get()->keyframeNumericAttribute("rx", frame, dagPath, euAngle.x); + Recorder::get()->keyframeNumericAttribute("ry", frame, dagPath, euAngle.y); + Recorder::get()->keyframeNumericAttribute("rz", frame, dagPath, euAngle.z); + }); + } +} + + + + + diff --git a/src/animations.h b/src/animations.h index e3fcced..b888db0 100644 --- a/src/animations.h +++ b/src/animations.h @@ -2,9 +2,14 @@ #define ANIMATIONS_H #include "singleton.h" +#include "recorder.h" + #include #include +#include + + class _Animations { public: @@ -20,6 +25,8 @@ class _Animations void applyAnimationsToMappedObjects(); + void recordingToggled(bool enabled); + void setSceneScale(float); float sceneScale() { return _sceneScale; } float timestamp; @@ -34,6 +41,10 @@ class _Animations QHash actorsMap; // faceId - json QHash facesMap; + + void animatePropOrTracker(QJsonObject obj, const MDagPath &dagPath); + + bool recordingEnabled = false; }; typedef Singleton<_Animations> Animations; diff --git a/src/constants.h b/src/constants.h index 82349dc..86d53db 100644 --- a/src/constants.h +++ b/src/constants.h @@ -9,8 +9,10 @@ static const int MINIMUM_WINDOW_HEIGHT = 300; static const int DEFAULT_RS_PORT = 14043; static const int DEFAULT_RS_API_PORT = 14053; -static const int VERSION_MAJOR = 1; -static const int VERSION_MINOR = 0; +static const int VERSION_MAJOR = 0; +static const int VERSION_MINOR = 1; static const int VERSION_PATCH = 0; +static const int RECEIVER_FPS = 30; + #endif // CONSTANTS_H diff --git a/src/main.cpp b/src/main.cpp index 8e35cbc..64f82f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,6 +11,10 @@ #include #include +#ifdef _WINDOWS + #pragma comment(lib,"OpenMayaAnim.lib") +#endif + QPointer rsmlScrollArea; QPointer rsmlWidget; QPointer workspaceControl; diff --git a/src/mapping.cpp b/src/mapping.cpp index 8011738..0cc3d30 100644 --- a/src/mapping.cpp +++ b/src/mapping.cpp @@ -10,15 +10,14 @@ #include #include #include +#include +#include #include #include #include #include #include - - -const QString MAPPING_FILED_NAME = "RokokoMapping"; - +#include _Mapping::_Mapping() @@ -137,16 +136,71 @@ _Mapping::_Mapping() // register callbacks MCallbackId beforeNewId = MSceneMessage::addCheckCallback(MSceneMessage::kBeforeNewCheck, [](bool* recCode, void* clientData) { + Q_UNUSED(clientData) Mapping::get()->clear(); *recCode = true; }); MCallbackId beforeOpenId = MSceneMessage::addCheckCallback(MSceneMessage::kBeforeOpenCheck, [](bool* recCode, void* clientData) { + Q_UNUSED(clientData) Mapping::get()->clear(); *recCode = true; }); callbacks.append(beforeNewId); callbacks.append(beforeOpenId); + faceShapeNames << "eyeBlinkLeft" + << "eyeLookDownLeft" + << "eyeLookInLeft" + << "eyeLookOutLeft" + << "eyeLookUpLeft" + << "eyeSquintLeft" + << "eyeWideLeft" + << "eyeBlinkRight" + << "eyeLookDownRight" + << "eyeLookInRight" + << "eyeLookOutRight" + << "eyeLookUpRight" + << "eyeSquintRight" + << "eyeWideRight" + << "jawForward" + << "jawLeft" + << "jawRight" + << "jawOpen" + << "mouthClose" + << "mouthFunnel" + << "mouthPucker" + << "mouthLeft" + << "mouthRight" + << "mouthSmileLeft" + << "mouthSmileRight" + << "mouthFrownLeft" + << "mouthFrownRight" + << "mouthDimpleLeft" + << "mouthDimpleRight" + << "mouthStretchLeft" + << "mouthStretchRight" + << "mouthRollLower" + << "mouthRollUpper" + << "mouthShrugLower" + << "mouthShrugUpper" + << "mouthPressLeft" + << "mouthPressRight" + << "mouthLowerDownLeft" + << "mouthLowerDownRight" + << "mouthUpperUpLeft" + << "mouthUpperUpRight" + << "browDownLeft" + << "browDownRight" + << "browInnerUp" + << "browOuterUpLeft" + << "browOuterUpRight" + << "cheekPuff" + << "cheekSquintLeft" + << "cheekSquintRight" + << "noseSneerLeft" + << "noseSneerRight" + << "tongueOut"; + } void _Mapping::mapRSObjectToSelection(QString rsObjectID) @@ -156,34 +210,11 @@ void _Mapping::mapRSObjectToSelection(QString rsObjectID) QString cmdString = cmdFile.readAll(); cmdFile.close(); - cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME); + cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME.asChar()); cmdString.replace("RS_ID_TAG", rsObjectID); MGlobal::executeCommand(cmdString.toStdString().c_str()); - // put ids into objectsMap - MSelectionList ls; - MGlobal::getActiveSelectionList(ls); - MItSelectionList iter(ls); - while(!iter.isDone()) { - MObject object; - MStatus dpNodeStatus = iter.getDependNode(object); - MFnDependencyNode node(object); - if(!objectsMap.contains(rsObjectID, object)) - { - objectsMap.insert(rsObjectID, object); - std::cout << "Store: " << node.name().asChar() << "\n"; - } else { - std::cout << "Already mapped: " << node.name().asChar() << "\n"; - } - iter.next(); - } - - auto objects = objectsMap.values(rsObjectID); - for(auto obj : objects) { - MFnDependencyNode node(obj); - std::cout << node.name() << " < "; - } - std::cout << "\n"; + syncMapping(); } void _Mapping::unmapRSObject(QString rsObjectID, bool selected=false) @@ -193,7 +224,7 @@ void _Mapping::unmapRSObject(QString rsObjectID, bool selected=false) QString cmdString = cmdFile.readAll(); cmdFile.close(); - cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME); + cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME.asChar()); cmdString.replace("RS_ID_TAG", rsObjectID); cmdString.replace("SELECTED_ONLY", selected ? "true" : "false"); MGlobal::executeCommand(cmdString.toStdString().c_str()); @@ -242,7 +273,7 @@ void _Mapping::selectObjects(QString rsObjectID) QString cmdString = cmdFile.readAll(); cmdFile.close(); - cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME); + cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME.asChar()); cmdString.replace("RS_ID_TAG", rsObjectID); MGlobal::executeCommand(cmdString.toStdString().c_str()); } @@ -252,21 +283,58 @@ void _Mapping::syncMapping() // erase all objectsMap.clear(); - // iterate over all transform nodes + // sync transform nodes MItDependencyNodes nodesIt(MFn::kTransform); while(!nodesIt.isDone()) { - MObject object = nodesIt.item(); + MObject object = nodesIt.thisNode(); MFnDependencyNode fn(object); - MString fieldName(MAPPING_FILED_NAME.toStdString().c_str()); + MString fieldName(MAPPING_FILED_NAME.asChar()); bool attrFound = fn.hasAttribute(fieldName); if(attrFound) { - MString rsIdValue = fn.findPlug(fieldName).asString(); + MStatus plugFound; + MString rsIdValue = fn.findPlug(fieldName, plugFound).asString(); objectsMap.insert(rsIdValue.asChar(), object); std::cout << "sync object: " << rsIdValue.asChar() << "\n"; } nodesIt.next(); } + + // sync blend shapes + MItDependencyNodes bsIt(MFn::kBlendShape); + while(!bsIt.isDone()) { + MObject object = bsIt.thisNode(); + MFnDependencyNode fn(object); + MString faceMappingAttributeName(FACE_MAPPING_FILED_NAME.asChar()); + bool mappingFound = fn.hasAttribute(faceMappingAttributeName); + if(mappingFound) { + // get rs id + MFnDependencyNode bsFn(object); + MStatus plugFound; + MPlug faceMappingPlug = bsFn.findPlug(FACE_MAPPING_FILED_NAME.asChar(), true, &plugFound); + if (!faceMappingPlug.isNull()) { + if(faceMappingPlug.isCompound()) { + unsigned int numChildren = faceMappingPlug.numChildren(); + MPlug faceIdPlug; + for (unsigned int i = 0; i < numChildren; ++i) { + MPlug childPlug = faceMappingPlug.child(i); + if(childPlug.partialName() == PREFIXED_FACE_ID) { + faceIdPlug = childPlug; + break; + } + } + if(!faceIdPlug.isNull()) + { + MString faceId = faceIdPlug.asString(); + objectsMap.insert(faceId.asChar(), object); + std::cout << "sync face: " << faceId.asChar() << "\n"; + } + } + } + } + + bsIt.next(); + } } bool _Mapping::mapActorToCurrentMayaCharacter(QString actorID) @@ -278,9 +346,11 @@ bool _Mapping::mapActorToCurrentMayaCharacter(QString actorID) return false; } // find hips joint by character name and NAME_Hips pattern + // TODO: check if namespace break this MSelectionList hipsLs; - QString hipsBoneName = QString("%1_Hips").arg(activeCharacterName); - MStatus hipsFound = MGlobal::getSelectionListByName(MString(hipsBoneName.toStdString().c_str()), hipsLs); + QString charHipsCmd = QString("hikGetSkNode(\"%1\", 1);").arg(activeCharacterName); + MString hipsBoneName = MGlobal::executeCommandStringResult(charHipsCmd.toStdString().c_str()); + MStatus hipsFound = MGlobal::getSelectionListByName(hipsBoneName, hipsLs); if(hipsFound != MStatus::kSuccess) { Utils::spawnMayaError(QString("Unable to map character! %1").arg(activeCharacterName)); return false; @@ -303,7 +373,7 @@ void _Mapping::unmapMayaObjectByName(QString mayaObjecName) QString cmdString = cmdFile.readAll(); cmdFile.close(); - cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME); + cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME.asChar()); cmdString.replace("MAYA_OBJECT_NAME", mayaObjecName); MGlobal::executeCommand(cmdString.toStdString().c_str()); @@ -311,15 +381,15 @@ void _Mapping::unmapMayaObjectByName(QString mayaObjecName) } -void _Mapping::setOrCreateRSIdAttribute(QString mayaObjecName, QString value) +void _Mapping::setOrCreateRSIdAttribute(QString mayaObjecName, QString rsId) { QFile cmdFile(":/resources/setOrCreateRSIdAttribute.mel"); cmdFile.open(QFile::ReadOnly); QString cmdString = cmdFile.readAll(); cmdFile.close(); - cmdString.replace("RS_ID_TAG", value); - cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME); + cmdString.replace("RS_ID_TAG", rsId); + cmdString.replace("MAPPING_FIELD_NAME", MAPPING_FILED_NAME.asChar()); cmdString.replace("MAYA_OBJECT_NAME", mayaObjecName); MGlobal::executeCommand(cmdString.toStdString().c_str()); } @@ -348,10 +418,267 @@ QString _Mapping::getCurrentMayaCharacter() return QString(currentCharacterName.asChar()); } +void _Mapping::mapFaceToMayaObject(QString mayaObjecName, QString rsId) +{ + // first grab object ref + MSelectionList ls; + MGlobal::getSelectionListByName(mayaObjecName.toStdString().c_str(), ls); + + if(ls.length() == 0) + return; + + MObject object; + ls.getDependNode(0, object); + MFnDependencyNode fn(object); + + // fetch selected node connected blendshapes + QFile cmdFile(":/resources/fetchBlendShapes.mel"); + cmdFile.open(QFile::ReadOnly); + QString cmdString = cmdFile.readAll(); + cmdFile.close(); + cmdString.replace("MAYA_OBJECT_NAME", fn.name().asChar()); + + cmdString.replace("SEPARATOR", BS_SEPARATOR); + MString connectedBlendShapes = MGlobal::executeCommandStringResult(cmdString.toStdString().c_str()); + if(connectedBlendShapes.length() == 0) { + Utils::spawnMayaError("Object have no blend shapes connected!"); + return; + } + QStringList blendShapesList = QString(connectedBlendShapes.asChar()).split(BS_SEPARATOR); + + // for each blendshape + // creafe face attributes + + MFnTypedAttribute tAttr; + MFnCompoundAttribute compound; + compound.setKeyable(false); + compound.setStorable(true); + compound.setWritable(true); + compound.setReadable(true); + + struct Local { + static void addAttr(MFnCompoundAttribute& parent, MFnTypedAttribute &tAttr, const MString& fieldName) { + MString prefixedName = BLEND_SHAPE_PREFIX + fieldName; + MObject obj = tAttr.create(prefixedName, prefixedName, MFnData::kString); + parent.addChild(obj); + } + + static void setAttributeString(MFnDependencyNode& node, const MString& fieldName, const MString& value) { + MString prefixedName = BLEND_SHAPE_PREFIX + fieldName; + MPlug plug = node.findPlug(prefixedName, true); + plug.setString(value); + } + }; + + for(QString bsNodeName : blendShapesList) { + MSelectionList bsls; + MGlobal::getSelectionListByName(bsNodeName.toStdString().c_str(), bsls); + + if(bsls.length() == 0) + return; + + MObject object; + bsls.getDependNode(0, object); + MFnDependencyNode fn(object); + + if(fn.hasAttribute(FACE_MAPPING_FILED_NAME)) { + Utils::mayaPrintMessage(QString("%1 of %2 node already mapped!").arg(bsNodeName, mayaObjecName)); + continue; + } + + // create compound attribute + MObject compoundObj = compound.create("RokokoFaceMapping", "RokokoFaceMapping"); + + // create face id attribute + Local::addAttr(compound, tAttr, "FaceId"); + Local::addAttr(compound, tAttr, "eyeBlinkLeft"); + Local::addAttr(compound, tAttr, "eyeLookDownLeft"); + Local::addAttr(compound, tAttr, "eyeLookInLeft"); + Local::addAttr(compound, tAttr, "eyeLookOutLeft"); + Local::addAttr(compound, tAttr, "eyeLookUpLeft"); + Local::addAttr(compound, tAttr, "eyeSquintLeft"); + Local::addAttr(compound, tAttr, "eyeWideLeft"); + Local::addAttr(compound, tAttr, "eyeBlinkRight"); + Local::addAttr(compound, tAttr, "eyeLookDownRight"); + Local::addAttr(compound, tAttr, "eyeLookInRight"); + Local::addAttr(compound, tAttr, "eyeLookOutRight"); + Local::addAttr(compound, tAttr, "eyeLookUpRight"); + Local::addAttr(compound, tAttr, "eyeSquintRight"); + Local::addAttr(compound, tAttr, "eyeWideRight"); + Local::addAttr(compound, tAttr, "jawForward"); + Local::addAttr(compound, tAttr, "jawLeft"); + Local::addAttr(compound, tAttr, "jawRight"); + Local::addAttr(compound, tAttr, "jawOpen"); + Local::addAttr(compound, tAttr, "mouthClose"); + Local::addAttr(compound, tAttr, "mouthFunnel"); + Local::addAttr(compound, tAttr, "mouthPucker"); + Local::addAttr(compound, tAttr, "mouthLeft"); + Local::addAttr(compound, tAttr, "mouthRight"); + Local::addAttr(compound, tAttr, "mouthSmileLeft"); + Local::addAttr(compound, tAttr, "mouthSmileRight"); + Local::addAttr(compound, tAttr, "mouthFrownLeft"); + Local::addAttr(compound, tAttr, "mouthFrownRight"); + Local::addAttr(compound, tAttr, "mouthDimpleLeft"); + Local::addAttr(compound, tAttr, "mouthDimpleRight"); + Local::addAttr(compound, tAttr, "mouthStretchLeft"); + Local::addAttr(compound, tAttr, "mouthStretchRight"); + Local::addAttr(compound, tAttr, "mouthRollLower"); + Local::addAttr(compound, tAttr, "mouthRollUpper"); + Local::addAttr(compound, tAttr, "mouthShrugLower"); + Local::addAttr(compound, tAttr, "mouthShrugUpper"); + Local::addAttr(compound, tAttr, "mouthPressLeft"); + Local::addAttr(compound, tAttr, "mouthPressRight"); + Local::addAttr(compound, tAttr, "mouthLowerDownLeft"); + Local::addAttr(compound, tAttr, "mouthLowerDownRight"); + Local::addAttr(compound, tAttr, "mouthUpperUpLeft"); + Local::addAttr(compound, tAttr, "mouthUpperUpRight"); + Local::addAttr(compound, tAttr, "browDownLeft"); + Local::addAttr(compound, tAttr, "browDownRight"); + Local::addAttr(compound, tAttr, "browInnerUp"); + Local::addAttr(compound, tAttr, "browOuterUpLeft"); + Local::addAttr(compound, tAttr, "browOuterUpRight"); + Local::addAttr(compound, tAttr, "cheekPuff"); + Local::addAttr(compound, tAttr, "cheekSquintLeft"); + Local::addAttr(compound, tAttr, "cheekSquintRight"); + Local::addAttr(compound, tAttr, "noseSneerLeft"); + Local::addAttr(compound, tAttr, "noseSneerRight"); + Local::addAttr(compound, tAttr, "tongueOut"); + + // create shapes string attributes + fn.addAttribute(compoundObj); + + // set values + Local::setAttributeString(fn, "FaceId", rsId.toStdString().c_str()); + Local::setAttributeString(fn, "eyeBlinkLeft", "eyeBlinkLeft"); + Local::setAttributeString(fn, "eyeLookDownLeft", "eyeLookDownLeft"); + Local::setAttributeString(fn, "eyeLookInLeft", "eyeLookInLeft"); + Local::setAttributeString(fn, "eyeLookOutLeft", "eyeLookOutLeft"); + Local::setAttributeString(fn, "eyeLookUpLeft", "eyeLookUpLeft"); + Local::setAttributeString(fn, "eyeSquintLeft", "eyeSquintLeft"); + Local::setAttributeString(fn, "eyeWideLeft", "eyeWideLeft"); + Local::setAttributeString(fn, "eyeBlinkRight", "eyeBlinkRight"); + Local::setAttributeString(fn, "eyeLookDownRight", "eyeLookDownRight"); + Local::setAttributeString(fn, "eyeLookInRight", "eyeLookInRight"); + Local::setAttributeString(fn, "eyeLookOutRight", "eyeLookOutRight"); + Local::setAttributeString(fn, "eyeLookUpRight", "eyeLookUpRight"); + Local::setAttributeString(fn, "eyeSquintRight", "eyeSquintRight"); + Local::setAttributeString(fn, "eyeWideRight", "eyeWideRight"); + Local::setAttributeString(fn, "jawForward", "jawForward"); + Local::setAttributeString(fn, "jawLeft", "jawLeft"); + Local::setAttributeString(fn, "jawRight", "jawRight"); + Local::setAttributeString(fn, "jawOpen", "jawOpen"); + Local::setAttributeString(fn, "mouthClose", "mouthClose"); + Local::setAttributeString(fn, "mouthFunnel", "mouthFunnel"); + Local::setAttributeString(fn, "mouthPucker", "mouthPucker"); + Local::setAttributeString(fn, "mouthLeft", "mouthLeft"); + Local::setAttributeString(fn, "mouthRight", "mouthRight"); + Local::setAttributeString(fn, "mouthSmileLeft", "mouthSmileLeft"); + Local::setAttributeString(fn, "mouthSmileRight", "mouthSmileRight"); + Local::setAttributeString(fn, "mouthFrownLeft", "mouthFrownLeft"); + Local::setAttributeString(fn, "mouthFrownRight", "mouthFrownRight"); + Local::setAttributeString(fn, "mouthDimpleLeft", "mouthDimpleLeft"); + Local::setAttributeString(fn, "mouthDimpleRight", "mouthDimpleRight"); + Local::setAttributeString(fn, "mouthStretchLeft", "mouthStretchLeft"); + Local::setAttributeString(fn, "mouthStretchRight", "mouthStretchRight"); + Local::setAttributeString(fn, "mouthRollLower", "mouthRollLower"); + Local::setAttributeString(fn, "mouthRollUpper", "mouthRollUpper"); + Local::setAttributeString(fn, "mouthShrugLower", "mouthShrugLower"); + Local::setAttributeString(fn, "mouthShrugUpper", "mouthShrugUpper"); + Local::setAttributeString(fn, "mouthPressLeft", "mouthPressLeft"); + Local::setAttributeString(fn, "mouthPressRight", "mouthPressRight"); + Local::setAttributeString(fn, "mouthLowerDownLeft", "mouthLowerDownLeft"); + Local::setAttributeString(fn, "mouthLowerDownRight", "mouthLowerDownRight"); + Local::setAttributeString(fn, "mouthUpperUpLeft", "mouthUpperUpLeft"); + Local::setAttributeString(fn, "mouthUpperUpRight", "mouthUpperUpRight"); + Local::setAttributeString(fn, "browDownLeft", "browDownLeft"); + Local::setAttributeString(fn, "browDownRight", "browDownRight"); + Local::setAttributeString(fn, "browInnerUp", "browInnerUp"); + Local::setAttributeString(fn, "browOuterUpLeft", "browOuterUpLeft"); + Local::setAttributeString(fn, "browOuterUpRight", "browOuterUpRight"); + Local::setAttributeString(fn, "cheekPuff", "cheekPuff"); + Local::setAttributeString(fn, "cheekSquintLeft", "cheekSquintLeft"); + Local::setAttributeString(fn, "cheekSquintRight", "cheekSquintRight"); + Local::setAttributeString(fn, "noseSneerLeft", "noseSneerLeft"); + Local::setAttributeString(fn, "noseSneerRight", "noseSneerRight"); + Local::setAttributeString(fn, "tongueOut", "tongueOut"); + } + + syncMapping(); +} + +void _Mapping::unmapFaceFromMayaObject(QString mayaObjecName) +{ + // grab object ref + MSelectionList ls; + MGlobal::getSelectionListByName(mayaObjecName.toStdString().c_str(), ls); + + if(ls.length() == 0) + return; + + MObject object; + ls.getDependNode(0, object); + MFnDependencyNode fn(object); + + // fetch connected blendshapes + QFile cmdFile(":/resources/fetchBlendShapes.mel"); + cmdFile.open(QFile::ReadOnly); + QString cmdString = cmdFile.readAll(); + cmdFile.close(); + cmdString.replace("MAYA_OBJECT_NAME", fn.name().asChar()); + + cmdString.replace("SEPARATOR", BS_SEPARATOR); + MString connectedBlendShapes = MGlobal::executeCommandStringResult(cmdString.toStdString().c_str()); + QStringList blendShapesList = QString(connectedBlendShapes.asChar()).split(BS_SEPARATOR); + + // iterate over blend shapes and remove face mapping compound attribute + for(QString bsNodeName : blendShapesList) + { + MSelectionList bsls; + MGlobal::getSelectionListByName(bsNodeName.toStdString().c_str(), bsls); + + if(bsls.length() == 0) + return; + + MObject bsObject; + bsls.getDependNode(0, bsObject); + MFnDependencyNode fn(bsObject); + if (fn.hasAttribute(FACE_MAPPING_FILED_NAME)) + { + MPlug plug = fn.findPlug(FACE_MAPPING_FILED_NAME, true); + fn.removeAttribute(plug.attribute()); + } + } + + // sync mapping + syncMapping(); +} + +void _Mapping::unmapAllFaces(QString rsId) +{ + // iterate over all blend shape nodes + MItDependencyNodes bsIterator(MFn::kBlendShape); + + while (!bsIterator.isDone()) { + MFnDependencyNode node(bsIterator.thisNode()); + if (node.hasAttribute(FACE_MAPPING_FILED_NAME.asChar())) { + MPlug plug = node.findPlug(FACE_MAPPING_FILED_NAME.asChar(), true); + + MPlug faceIdPlug = node.findPlug(PREFIXED_FACE_ID, true); + MString faceIdValue; + faceIdPlug.getValue(faceIdValue); + + // remove face mapping with passed id + if(faceIdValue.asChar() == rsId) { + node.removeAttribute(plug.attribute()); + } + } + bsIterator.next(); + } +} + void _Mapping::clear() { objectsMap.clear(); -// boneMapping.clear(); } void _Mapping::resetCallbacks() diff --git a/src/mapping.h b/src/mapping.h index 6a2375a..7b6c57c 100644 --- a/src/mapping.h +++ b/src/mapping.h @@ -7,8 +7,15 @@ #include #include #include +#include +const MString MAPPING_FILED_NAME = "RokokoMapping"; +const MString FACE_MAPPING_FILED_NAME = "RokokoFaceMapping"; +const MString BLEND_SHAPE_PREFIX = "RKK_"; +const MString PREFIXED_FACE_ID = BLEND_SHAPE_PREFIX + "FaceId"; +const QString BS_SEPARATOR("3996e3a0"); + class _Mapping { @@ -27,17 +34,22 @@ class _Mapping bool mapActorToCurrentMayaCharacter(QString actorID); void unmapMayaObjectByName(QString mayaObjecName); - void setOrCreateRSIdAttribute(QString mayaObjecName, QString rsObjectID); + void setOrCreateRSIdAttribute(QString mayaObjecName, QString rsId); void createHIKForActor(QString rsObjectID); QString getCurrentMayaCharacter(); + void mapFaceToMayaObject(QString mayaObjecName, QString rsId); + void unmapFaceFromMayaObject(QString mayaObjecName=""); + void unmapAllFaces(QString rsId); + void clear(); void resetCallbacks(); const QMultiMap &getObjectMapping(); const QHash &getBoneMapping(); const QHash &getStudioTPose(); + const QStringList &getFaceShapeNames() { return faceShapeNames; }; private: // prop id - maya object QMultiMap objectsMap; @@ -46,6 +58,9 @@ class _Mapping // studio t-pose QHash studioTPose; + // studio face shape names + QStringList faceShapeNames; + MCallbackIdArray callbacks; }; diff --git a/src/receiverworker.cpp b/src/receiverworker.cpp index ceb18e1..e562f8d 100644 --- a/src/receiverworker.cpp +++ b/src/receiverworker.cpp @@ -13,7 +13,7 @@ DataReceivingWorker::DataReceivingWorker(QObject* parent) connect(socket, QOverload::of(&QAbstractSocket::error), this, &DataReceivingWorker::onSocketError); connect(&hearbeat, &QTimer::timeout, this, &DataReceivingWorker::onHearBeat); - hearbeat.setInterval(33); + hearbeat.setInterval(1000 / RECEIVER_FPS); hearbeat.start(); } diff --git a/src/recorder.cpp b/src/recorder.cpp new file mode 100644 index 0000000..3d72572 --- /dev/null +++ b/src/recorder.cpp @@ -0,0 +1,100 @@ +#include "utils.h" +#include "constants.h" +#include "recorder.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +_Recorder::_Recorder() +{ + +} + +void _Recorder::recordPropOrTracker(float timestamp, std::function foo) +{ + recordedData[timestamp].append(foo); +} + +void _Recorder::recordFace(float timestamp, std::function foo) +{ + recordedData[timestamp].append(foo); +} + +void _Recorder::recordBone(float timestamp, std::function foo) +{ + recordedData[timestamp].append(foo); +} + +void _Recorder::finalizeRecording() +{ + QList timestamps = sortedTimeStamps(); + + MTime currentTime = MAnimControl::currentTime(); + + int index = -1 + currentTime.value(); + for(float ts: timestamps) + { + index++; + int frame = index; + + auto delegates = recordedData[ts]; + + // bake + for (auto foo : delegates) foo(frame); + + } + + recordedData.clear(); +} + +void _Recorder::recordingToggled(bool enabled) +{ + mRecordingStartTime = MAnimControl::currentTime().value(); + isRecording = enabled; +} + +void _Recorder::keyframeNumericAttribute(MString attrName, int frame, MDagPath dagPath, double value) +{ + MFnAnimCurve curveFn; + MFnDagNode fn(dagPath); + MPlug plug = fn.findPlug(attrName, false, nullptr); + keyframeNumericAttribute(frame, plug, value); +} + +void _Recorder::keyframeNumericAttribute(int frame, MPlug plug, double value) +{ + MFnAnimCurve curveFn; + MFnAnimCurve::AnimCurveType curveType = curveFn.timedAnimCurveTypeForPlug(plug); + + // get or create curve function set + MObject curve; + MPlugArray srcPlugs; + if (plug.connectedTo(srcPlugs, true, false)) + curveFn.setObject(srcPlugs[0].node()); + else + curve = curveFn.create(plug, curveType); + + curveFn.addKey(MTime(frame, MTime::uiUnit()), value); +} + +QList _Recorder::sortedTimeStamps() +{ + QList timestamps = recordedData.keys(); + std::sort(timestamps.begin(), timestamps.end()); + return timestamps; +} diff --git a/src/recorder.h b/src/recorder.h new file mode 100644 index 0000000..b87ba36 --- /dev/null +++ b/src/recorder.h @@ -0,0 +1,50 @@ +#ifndef RECORDER_H +#define RECORDER_H + +#include + +#include "singleton.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + + +class _Recorder +{ +public: + _Recorder(); + + void recordPropOrTracker(float timestamp, std::function foo); + void recordFace(float timestamp, std::function foo); + void recordBone(float timestamp, std::function foo); + + void finalizeRecording(); + + void recordingToggled(bool enabled); + + void keyframeNumericAttribute(MString attrName, int frame, MDagPath dagPath, double value); + void keyframeNumericAttribute(int frame, MPlug plug, double value); + + bool recordingEnabled() { return isRecording; } + unsigned int numFramesAvailable() { return recordedData.count(); } + float recordingStartTime() {return mRecordingStartTime ;} + + QList sortedTimeStamps(); +private: + // timestamp - set keyframe function + QHash>> recordedData; + float mRecordingStartTime; + bool isRecording = false; +}; + + +typedef Singleton<_Recorder> Recorder; + +#endif // RECORDER_H diff --git a/src/ui/receivercontent.cpp b/src/ui/receivercontent.cpp index ce208b4..720d042 100644 --- a/src/ui/receivercontent.cpp +++ b/src/ui/receivercontent.cpp @@ -3,7 +3,6 @@ #include "animations.h" #include "mapping.h" #include "utils.h" -#include "ui/recordbutton.h" #include #include @@ -21,6 +20,10 @@ #include #include +#include +#include +#include +#include enum class RSObjectType : uint8_t { @@ -84,13 +87,14 @@ ReceiverContent::ReceiverContent(QWidget* parent) : QWidget(parent) startReceiverBtn = new Button(this, receiverBtnParams); startReceiverBtn->setCheckable(true); mainLayout->addWidget(startReceiverBtn); - connect(startReceiverBtn, &Button::toggled, this, &ReceiverContent::onReceiveToggled); + connect(startReceiverBtn, &Button::toggled, this, &ReceiverContent::onReceiverToggled); // record button - RecordButton* startRecordingBtn = new RecordButton(this); + startRecordingBtn = new RecordButton(this); startRecordingBtn->setEnabled(false); mainLayout->addWidget(startRecordingBtn); connect(startReceiverBtn, &QPushButton::toggled, startRecordingBtn, &QPushButton::setEnabled); + connect(startRecordingBtn, &QPushButton::toggled, this, &ReceiverContent::recordingToggled); // error label statusLabel = new QLabel("", this); @@ -140,7 +144,7 @@ ReceiverContent::~ReceiverContent() } -void ReceiverContent::onReceiveToggled(bool checked) +void ReceiverContent::onReceiverToggled(bool checked) { if(checked) { @@ -161,9 +165,7 @@ void ReceiverContent::prepareContextMenu(const QPoint &pos) // this item can't be mapped if(itemId.isEmpty()) return; - // check item type and create special menus for actors - // ... - + // check item type and create special menus for each type QMenu menu(this); if(itemType == RSObjectType::PROP || itemType == RSObjectType::TRACKER) { @@ -222,11 +224,54 @@ void ReceiverContent::prepareContextMenu(const QPoint &pos) }); } + if(itemType == RSObjectType::FACE) { + menu.addAction("Map to selected objects", [=](){ + MSelectionList ls; + MGlobal::getActiveSelectionList(ls); + if(ls.length() > 0) + { + MItSelectionList it(ls); + while(!it.isDone()) { + MDagPath objPath; + it.getDagPath(objPath); + Mapping::get()->mapFaceToMayaObject(objPath.fullPathName().asChar(), itemId); + + it.next(); + } + + } + }); + menu.addAction("Unmap selected objects", [=](){ + MSelectionList ls; + MGlobal::getActiveSelectionList(ls); + if(ls.length() > 0) + { + MItSelectionList it(ls); + while(!it.isDone()) { + MDagPath objPath; + it.getDagPath(objPath); + Mapping::get()->unmapFaceFromMayaObject(objPath.fullPathName().asChar()); + it.next(); + } + } + }); + + menu.addAction("Unmap all", [=](){ + Mapping::get()->unmapAllFaces(itemId); + }); + + } + menu.exec(treeWidget->mapToGlobal(pos)); } } +void ReceiverContent::recordingToggled(bool checked) +{ + Animations::get()->recordingToggled(checked); +} + void ReceiverContent::populateTree() { QTimer::singleShot(250, [&](){ diff --git a/src/ui/receivercontent.h b/src/ui/receivercontent.h index 349478a..b44978e 100644 --- a/src/ui/receivercontent.h +++ b/src/ui/receivercontent.h @@ -3,6 +3,7 @@ #include "receiverworker.h" #include "ui/button.h" +#include "ui/recordbutton.h" #include #include #include @@ -17,13 +18,15 @@ class ReceiverContent : public QWidget ReceiverContent(QWidget* parent=nullptr); ~ReceiverContent(); private: - void onReceiveToggled(bool); + void onReceiverToggled(bool); QSpinBox* portBox=nullptr; DataReceivingWorker* worker=nullptr; QLabel* statusLabel=nullptr; Button* startReceiverBtn=nullptr; QTreeWidget* treeWidget=nullptr; + RecordButton* startRecordingBtn=nullptr; void prepareContextMenu(const QPoint &pos); + void recordingToggled(bool checked); void populateTree(); void clearTreeWidget(); void reset(); diff --git a/src/ui/recordbutton.cpp b/src/ui/recordbutton.cpp index 801033b..d5f72ba 100644 --- a/src/ui/recordbutton.cpp +++ b/src/ui/recordbutton.cpp @@ -1,4 +1,8 @@ +#include "constants.h" #include "recordbutton.h" +#include "recorder.h" + +#include const ButtonParams recordButtonParams = { ":/resources/icon-stop-white-32.png", @@ -12,7 +16,11 @@ RecordButton::RecordButton(QWidget* parent) : Button(parent, recordButtonParams) { setCheckable(true); + recordedFramesFont = QFont("Consolas", 8); + updater.setInterval(RECEIVER_FPS * 2); + connect(&updater, &QTimer::timeout, this, &RecordButton::onHearbeat); + updater.start(); } void RecordButton::changeEvent(QEvent* e) @@ -27,3 +35,27 @@ void RecordButton::changeEvent(QEvent* e) update(); } } + +void RecordButton::paintEvent(QPaintEvent *event) +{ + Button::paintEvent(event); + // paint recorded frames num + QPainter painter(this); + if(Recorder::get()->recordingEnabled()) + { + float startTime = Recorder::get()->recordingStartTime(); + unsigned int recordedFrames = Recorder::get()->numFramesAvailable(); + QString text = QString("%1 - %2").arg(startTime).arg(startTime + recordedFrames); + QFontMetrics m(recordedFramesFont); + painter.setFont(recordedFramesFont); + int textWidth = m.horizontalAdvance(text); + QPoint p = rect().bottomRight() - QPoint(textWidth * 1.5, m.height() / 2); + painter.drawText(p, text); + } + +} + +void RecordButton::onHearbeat() +{ + update(); +} diff --git a/src/ui/recordbutton.h b/src/ui/recordbutton.h index e77285c..d41b6ec 100644 --- a/src/ui/recordbutton.h +++ b/src/ui/recordbutton.h @@ -3,6 +3,8 @@ #include "ui/button.h" #include +#include +#include class RecordButton : public Button @@ -12,6 +14,11 @@ class RecordButton : public Button RecordButton(QWidget* parent=nullptr); protected: void changeEvent(QEvent*) override; + void paintEvent(QPaintEvent *) override; +private: + void onHearbeat(); + QFont recordedFramesFont; + QTimer updater; }; #endif // RECORDBUTTON_H diff --git a/src/ui/rootwidget.cpp b/src/ui/rootwidget.cpp index 161fe4a..9b20541 100644 --- a/src/ui/rootwidget.cpp +++ b/src/ui/rootwidget.cpp @@ -49,13 +49,16 @@ RootWidget::RootWidget(QWidget *parent) }); // Updater category - CategoryHeader* updaterHeader = new CategoryHeader(this, "Updater"); - mainLayout->addWidget(updaterHeader, 0, Qt::AlignTop); - UpdaterContent* updaterContent = new UpdaterContent(this); - mainLayout->addWidget(updaterContent, 0, Qt::AlignTop); - connect(updaterHeader, &CategoryHeader::collapseStateChanged, [updaterContent](bool bCollapsed) { - updaterContent->setVisible(!bCollapsed); - }); + if(false) + { + CategoryHeader* updaterHeader = new CategoryHeader(this, "Updater"); + mainLayout->addWidget(updaterHeader, 0, Qt::AlignTop); + UpdaterContent* updaterContent = new UpdaterContent(this); + mainLayout->addWidget(updaterContent, 0, Qt::AlignTop); + connect(updaterHeader, &CategoryHeader::collapseStateChanged, [updaterContent](bool bCollapsed) { + updaterContent->setVisible(!bCollapsed); + }); + } // Info category CategoryHeader* infoHeader = new CategoryHeader(this, "Info"); diff --git a/src/utils.cpp b/src/utils.cpp index 8001fff..ab04f15 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,7 +1,12 @@ #include "utils.h" + #include +#include + #include +#include #include +#include void Utils::spawnMayaError(QString message) @@ -28,3 +33,19 @@ MQuaternion Utils::rsToMaya(MQuaternion rsRotation) return MQuaternion(-rsRotation.x, rsRotation.y, rsRotation.z, -rsRotation.w); } +void Utils::fillFaceWeightsMap(const MFnBlendShapeDeformer &bsFn, QHash &map) +{ + MStatus plugFound = MStatus::kFailure; + MPlug weightsArray = bsFn.findPlug("weight", false, &plugFound); + unsigned int weightsCount = weightsArray.numElements(); + for(unsigned int i=0; i < weightsCount; ++i) + { + MPlug shapePlug = weightsArray.elementByPhysicalIndex(i); + MString shapeName = shapePlug.partialName(false, false, false, true, false, false); + map.insert(shapeName.asChar(), shapePlug); + } + + if(plugFound == MStatus::kFailure) { + printf("Failed to find weight attribute!!"); + } +} diff --git a/src/utils.h b/src/utils.h index 8b1af94..84165a8 100644 --- a/src/utils.h +++ b/src/utils.h @@ -12,6 +12,7 @@ class Utils { static void mayaPrintMessage(QString message); static MVector rsToMaya(MVector); static MQuaternion rsToMaya(MQuaternion); + static void fillFaceWeightsMap(const MFnBlendShapeDeformer &bsFn, QHash &map); };