Add switch chain side action for pose editor

It's very easy to place the wrong side limbs on the pose graphic editor, especially when the reference sheet is not clear. Switch chain side will switch the YZ for the paired chain which contains the selected node. For example, if you select one node from LeftLimb1, and choose the switch chain side from context menu, all the bones, has the name pattern LeftLimb1_Joint* and RightLimb1_Joint*, the YZ of origin will be swapped.
master
Jeremy Hu 2018-11-10 07:56:58 +08:00
parent ddc837b47c
commit 69b0fbd68d
5 changed files with 104 additions and 0 deletions

View File

@ -3,6 +3,7 @@
#include <QClipboard> #include <QClipboard>
#include <QApplication> #include <QApplication>
#include <QMimeData> #include <QMimeData>
#include <QRegularExpression>
#include "posedocument.h" #include "posedocument.h"
#include "rigger.h" #include "rigger.h"
#include "util.h" #include "util.h"
@ -516,3 +517,80 @@ float PoseDocument::toOutcomeZ(float z)
return (1.0 - z) / m_outcomeScaleFactor; return (1.0 - z) / m_outcomeScaleFactor;
} }
QString PoseDocument::findBoneNameByNodeId(const QUuid &nodeId)
{
for (const auto &item: m_boneNameToIdsMap) {
if (nodeId == item.second.first || nodeId == item.second.second)
return item.first;
}
return QString();
}
void PoseDocument::switchChainSide(const std::set<QUuid> nodeIds)
{
QRegularExpression reJoints("^(Left|Right)([a-zA-Z]+\\d*)_(Joint\\d+)$");
std::set<QString> baseNames;
for (const auto &nodeId: nodeIds) {
QString boneName = findBoneNameByNodeId(nodeId);
if (boneName.isEmpty()) {
//qDebug() << "Find bone name for node failed:" << nodeId;
continue;
}
QRegularExpressionMatch match = reJoints.match(boneName);
if (!match.hasMatch()) {
//qDebug() << "Match bone name for side failed:" << boneName;
continue;
}
QString baseName = match.captured(2);
baseNames.insert(baseName);
}
auto switchYZ = [=](const QUuid &first, const QUuid &second) {
auto findFirstNode = nodeMap.find(first);
if (findFirstNode == nodeMap.end())
return;
auto findSecondNode = nodeMap.find(second);
if (findSecondNode == nodeMap.end())
return;
std::swap(findFirstNode->second.y, findSecondNode->second.y);
std::swap(findFirstNode->second.z, findSecondNode->second.z);
emit nodeOriginChanged(first);
emit nodeOriginChanged(second);
};
std::set<std::pair<QUuid, QUuid>> switchPairs;
for (const auto &baseName: baseNames) {
for (const auto &item: m_boneNameToIdsMap) {
QRegularExpressionMatch match = reJoints.match(item.first);
if (!match.hasMatch())
continue;
QString itemSide = match.captured(1);
QString itemBaseName = match.captured(2);
QString itemJointName = match.captured(3);
//qDebug() << "itemSide:" << itemSide << "itemBaseName:" << itemBaseName << "itemJointName:" << itemJointName;
if (itemBaseName == baseName && "Left" == itemSide) {
QString otherSide = "Right";
QString pairedName = otherSide + itemBaseName + "_" + itemJointName;
const auto findPaired = m_boneNameToIdsMap.find(pairedName);
if (findPaired == m_boneNameToIdsMap.end()) {
qDebug() << "Couldn't find paired name:" << pairedName;
continue;
}
//qDebug() << "Switched:" << pairedName;
switchPairs.insert({item.second.first, findPaired->second.first});
switchPairs.insert({item.second.second, findPaired->second.second});
}
}
}
for (const auto &pair: switchPairs) {
switchYZ(pair.first, pair.second);
}
//qDebug() << "switchedPairNum:" << switchPairs.size();
if (!switchPairs.empty())
emit parametersChanged();
}

View File

@ -49,6 +49,7 @@ public slots:
void moveNodeBy(QUuid nodeId, float x, float y, float z); void moveNodeBy(QUuid nodeId, float x, float y, float z);
void setNodeOrigin(QUuid nodeId, float x, float y, float z); void setNodeOrigin(QUuid nodeId, float x, float y, float z);
float findGroundY() const; float findGroundY() const;
void switchChainSide(const std::set<QUuid> nodeIds);
public: public:
static const float m_nodeRadius; static const float m_nodeRadius;
@ -57,6 +58,8 @@ public:
static const float m_outcomeScaleFactor; static const float m_outcomeScaleFactor;
private: private:
QString findBoneNameByNodeId(const QUuid &nodeId);
std::map<QString, std::pair<QUuid, QUuid>> m_boneNameToIdsMap; std::map<QString, std::pair<QUuid, QUuid>> m_boneNameToIdsMap;
QUuid m_groundPartId; QUuid m_groundPartId;
QUuid m_bonesPartId; QUuid m_bonesPartId;

View File

@ -70,6 +70,7 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_poseDocument, &PoseDocument::saveHistoryItem); connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_poseDocument, &PoseDocument::saveHistoryItem);
connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_poseDocument, &PoseDocument::undo); connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_poseDocument, &PoseDocument::undo);
connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_poseDocument, &PoseDocument::redo); connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_poseDocument, &PoseDocument::redo);
connect(graphicsWidget, &SkeletonGraphicsWidget::switchChainSide, m_poseDocument, &PoseDocument::switchChainSide);
connect(m_poseDocument, &PoseDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent); connect(m_poseDocument, &PoseDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent);

View File

@ -204,6 +204,12 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
contextMenu.addAction(&switchXzAction); contextMenu.addAction(&switchXzAction);
} }
QAction switchChainSideAction(tr("Switch Chain Side"), this);
if (m_nodePositionModifyOnly && hasNodeSelection()) {
connect(&switchChainSideAction, &QAction::triggered, this, &SkeletonGraphicsWidget::switchSelectedChainSide);
contextMenu.addAction(&switchChainSideAction);
}
QAction alignToLocalCenterAction(tr("Local Center"), this); QAction alignToLocalCenterAction(tr("Local Center"), this);
QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this); QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this);
QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this); QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this);
@ -977,6 +983,20 @@ void SkeletonGraphicsWidget::switchSelectedXZ()
emit groupOperationAdded(); emit groupOperationAdded();
} }
void SkeletonGraphicsWidget::switchSelectedChainSide()
{
if (m_rangeSelectionSet.empty())
return;
std::set<QUuid> nodeIdSet;
readSkeletonNodeAndEdgeIdSetFromRangeSelection(&nodeIdSet);
if (nodeIdSet.empty())
return;
emit switchChainSide(nodeIdSet);
emit groupOperationAdded();
}
void SkeletonGraphicsWidget::zoomSelected(float delta) void SkeletonGraphicsWidget::zoomSelected(float delta)
{ {
if (m_rangeSelectionSet.empty()) if (m_rangeSelectionSet.empty())

View File

@ -382,6 +382,7 @@ signals:
void setNodeBoneMark(QUuid nodeId, BoneMark mark); void setNodeBoneMark(QUuid nodeId, BoneMark mark);
void zoomRenderedModelBy(float delta); void zoomRenderedModelBy(float delta);
void switchNodeXZ(QUuid nodeId); void switchNodeXZ(QUuid nodeId);
void switchChainSide(std::set<QUuid> nodeIds);
void enableAllPositionRelatedLocks(); void enableAllPositionRelatedLocks();
void disableAllPositionRelatedLocks(); void disableAllPositionRelatedLocks();
public: public:
@ -470,6 +471,7 @@ public slots:
void setSelectedNodesBoneMark(BoneMark boneMark); void setSelectedNodesBoneMark(BoneMark boneMark);
void timeToRemoveDeferredNodesAndEdges(); void timeToRemoveDeferredNodesAndEdges();
void switchSelectedXZ(); void switchSelectedXZ();
void switchSelectedChainSide();
void shortcutDelete(); void shortcutDelete();
void shortcutAddMode(); void shortcutAddMode();
void shortcutUndo(); void shortcutUndo();