From 69b0fbd68df84d2fa4b7278ae62fac583b96c346 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sat, 10 Nov 2018 07:56:58 +0800 Subject: [PATCH] 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. --- src/posedocument.cpp | 78 ++++++++++++++++++++++++++++++++++ src/posedocument.h | 3 ++ src/poseeditwidget.cpp | 1 + src/skeletongraphicswidget.cpp | 20 +++++++++ src/skeletongraphicswidget.h | 2 + 5 files changed, 104 insertions(+) diff --git a/src/posedocument.cpp b/src/posedocument.cpp index 76ab9a8a..1dca089d 100644 --- a/src/posedocument.cpp +++ b/src/posedocument.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "posedocument.h" #include "rigger.h" #include "util.h" @@ -516,3 +517,80 @@ float PoseDocument::toOutcomeZ(float z) 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 nodeIds) +{ + QRegularExpression reJoints("^(Left|Right)([a-zA-Z]+\\d*)_(Joint\\d+)$"); + + std::set 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> 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(); +} diff --git a/src/posedocument.h b/src/posedocument.h index cb2e03bd..5e4ad536 100644 --- a/src/posedocument.h +++ b/src/posedocument.h @@ -49,6 +49,7 @@ public slots: void moveNodeBy(QUuid nodeId, float x, float y, float z); void setNodeOrigin(QUuid nodeId, float x, float y, float z); float findGroundY() const; + void switchChainSide(const std::set nodeIds); public: static const float m_nodeRadius; @@ -57,6 +58,8 @@ public: static const float m_outcomeScaleFactor; private: + QString findBoneNameByNodeId(const QUuid &nodeId); + std::map> m_boneNameToIdsMap; QUuid m_groundPartId; QUuid m_bonesPartId; diff --git a/src/poseeditwidget.cpp b/src/poseeditwidget.cpp index d4828e18..fc3afee2 100644 --- a/src/poseeditwidget.cpp +++ b/src/poseeditwidget.cpp @@ -70,6 +70,7 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, m_poseDocument, &PoseDocument::saveHistoryItem); connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_poseDocument, &PoseDocument::undo); connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_poseDocument, &PoseDocument::redo); + connect(graphicsWidget, &SkeletonGraphicsWidget::switchChainSide, m_poseDocument, &PoseDocument::switchChainSide); connect(m_poseDocument, &PoseDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 37cc1055..78201d31 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -204,6 +204,12 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) 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 alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this); QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this); @@ -977,6 +983,20 @@ void SkeletonGraphicsWidget::switchSelectedXZ() emit groupOperationAdded(); } +void SkeletonGraphicsWidget::switchSelectedChainSide() +{ + if (m_rangeSelectionSet.empty()) + return; + + std::set nodeIdSet; + readSkeletonNodeAndEdgeIdSetFromRangeSelection(&nodeIdSet); + if (nodeIdSet.empty()) + return; + + emit switchChainSide(nodeIdSet); + emit groupOperationAdded(); +} + void SkeletonGraphicsWidget::zoomSelected(float delta) { if (m_rangeSelectionSet.empty()) diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 1d2c593d..38653eba 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -382,6 +382,7 @@ signals: void setNodeBoneMark(QUuid nodeId, BoneMark mark); void zoomRenderedModelBy(float delta); void switchNodeXZ(QUuid nodeId); + void switchChainSide(std::set nodeIds); void enableAllPositionRelatedLocks(); void disableAllPositionRelatedLocks(); public: @@ -470,6 +471,7 @@ public slots: void setSelectedNodesBoneMark(BoneMark boneMark); void timeToRemoveDeferredNodesAndEdges(); void switchSelectedXZ(); + void switchSelectedChainSide(); void shortcutDelete(); void shortcutAddMode(); void shortcutUndo();