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
parent
ddc837b47c
commit
69b0fbd68d
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue