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);
};