Support onion skin in pose editor

Also add more buttons to speed up the pose editing.
Thanks @Toshiwoz for proposing this feature.
master
Jeremy Hu 2019-03-03 09:35:13 +09:30
parent 8c0156bc7a
commit 26353d530a
9 changed files with 380 additions and 115 deletions

View File

@ -25,3 +25,4 @@ Ruben Niculcea <https://github.com/RubenSandwich>
boynet <https://dust3d.discourse.group/u/boynet> boynet <https://dust3d.discourse.group/u/boynet>
fornclake <https://www.reddit.com/user/fornclake> fornclake <https://www.reddit.com/user/fornclake>
bvanevery <https://www.reddit.com/user/bvanevery> bvanevery <https://www.reddit.com/user/bvanevery>
Toshio Araki <https://github.com/Toshiwoz>

View File

@ -1,6 +1,6 @@
<a href="https://dust3d.org" target="_blank"><image src="https://raw.githubusercontent.com/huxingyi/dust3d/master/dust3d-logo.png" width="66" height="58"></a> <a href="https://dust3d.org" target="_blank"><image src="https://raw.githubusercontent.com/huxingyi/dust3d/master/dust3d-logo.png" width="66" height="58"></a>
[![appveyor status](https://ci.appveyor.com/api/projects/status/github/huxingyi/dust3d?branch=master&svg=true)](https://ci.appveyor.com/project/huxingyi/dust3d) [![travis status](https://travis-ci.org/huxingyi/dust3d.svg?branch=master)](https://travis-ci.org/huxingyi/dust3d) [![readthedocs status](https://readthedocs.org/projects/dust3d/badge/?version=latest)](http://docs.dust3d.org/en/latest/?badge=latest) [![](https://img.shields.io/twitter/follow/jeremyhu2016.svg?label=%20%40jeremyhu2016&style=social)](https://twitter.com/jeremyhu2016) [![](https://img.shields.io/badge/Mailing%20List%20-Join-blue.svg)](https://www.freelists.org/list/dust3d) [![](https://img.shields.io/discourse/https/dust3d.discourse.group/status.svg)](https://dust3d.discourse.group/) [![appveyor status](https://ci.appveyor.com/api/projects/status/github/huxingyi/dust3d?branch=master&svg=true)](https://ci.appveyor.com/project/huxingyi/dust3d) [![travis status](https://travis-ci.org/huxingyi/dust3d.svg?branch=master)](https://travis-ci.org/huxingyi/dust3d) [![readthedocs status](https://readthedocs.org/projects/dust3d/badge/?version=latest)](http://docs.dust3d.org/en/latest/?badge=latest) [![](https://img.shields.io/twitter/follow/jeremyhu2016.svg?label=%20%40jeremyhu2016&style=social)](https://twitter.com/jeremyhu2016) [![](https://img.shields.io/badge/mailing%20list%20-join-blue.svg)](https://www.freelists.org/list/dust3d) [![](https://img.shields.io/discourse/https/dust3d.discourse.group/status.svg)](https://dust3d.discourse.group/)
Overview Overview
---------- ----------

View File

@ -11,7 +11,7 @@
#include "snapshot.h" #include "snapshot.h"
#include "snapshotxml.h" #include "snapshotxml.h"
const float PoseDocument::m_nodeRadius = 0.005; const float PoseDocument::m_nodeRadius = 0.015;
const float PoseDocument::m_groundPlaneHalfThickness = 0.005 / 4; const float PoseDocument::m_groundPlaneHalfThickness = 0.005 / 4;
const bool PoseDocument::m_hideRootAndVirtual = true; const bool PoseDocument::m_hideRootAndVirtual = true;
const float PoseDocument::m_outcomeScaleFactor = 0.5; const float PoseDocument::m_outcomeScaleFactor = 0.5;
@ -34,16 +34,32 @@ bool PoseDocument::originSettled() const
bool PoseDocument::isNodeEditable(QUuid nodeId) const bool PoseDocument::isNodeEditable(QUuid nodeId) const
{ {
Q_UNUSED(nodeId); if (m_otherIds.find(nodeId) != m_otherIds.end())
return false;
return true; return true;
} }
bool PoseDocument::isEdgeEditable(QUuid edgeId) const bool PoseDocument::isEdgeEditable(QUuid edgeId) const
{ {
Q_UNUSED(edgeId); if (m_otherIds.find(edgeId) != m_otherIds.end())
return false;
return true; return true;
} }
bool PoseDocument::isNodeDeactivated(QUuid nodeId) const
{
if (m_otherIds.find(nodeId) != m_otherIds.end())
return true;
return false;
}
bool PoseDocument::isEdgeDeactivated(QUuid edgeId) const
{
if (m_otherIds.find(edgeId) != m_otherIds.end())
return true;
return false;
}
void PoseDocument::copyNodes(std::set<QUuid> nodeIdSet) const void PoseDocument::copyNodes(std::set<QUuid> nodeIdSet) const
{ {
std::map<QString, std::map<QString, QString>> parameters; std::map<QString, std::map<QString, QString>> parameters;
@ -129,11 +145,17 @@ void PoseDocument::updateTurnaround(const QImage &image)
emit turnaroundChanged(); emit turnaroundChanged();
} }
void PoseDocument::updateOtherFramesParameters(const std::vector<std::map<QString, std::map<QString, QString>>> &otherFramesParameters)
{
m_otherFramesParameters = otherFramesParameters;
}
void PoseDocument::reset() void PoseDocument::reset()
{ {
nodeMap.clear(); nodeMap.clear();
edgeMap.clear(); edgeMap.clear();
partMap.clear(); partMap.clear();
m_otherIds.clear();
m_boneNameToIdsMap.clear(); m_boneNameToIdsMap.clear();
m_bonesPartId = QUuid(); m_bonesPartId = QUuid();
m_groundPartId = QUuid(); m_groundPartId = QUuid();
@ -148,20 +170,11 @@ void PoseDocument::clearHistories()
m_redoItems.clear(); m_redoItems.clear();
} }
void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones, void PoseDocument::updateBonesAndHeightAboveGroundLevelFromParameters(std::vector<RiggerBone> *bones,
float *heightAboveGroundLevel,
const std::map<QString, std::map<QString, QString>> &parameters) const std::map<QString, std::map<QString, QString>> &parameters)
{ {
if (nullptr == rigBones || rigBones->empty()) { for (auto &bone: *bones) {
m_riggerBones.clear();
return;
}
if (&m_riggerBones != rigBones)
m_riggerBones = *rigBones;
float heightAboveGroundLevel = 0;
std::vector<RiggerBone> bones = *rigBones;
for (auto &bone: bones) {
const auto findParameterResult = parameters.find(bone.name); const auto findParameterResult = parameters.find(bone.name);
if (findParameterResult == parameters.end()) if (findParameterResult == parameters.end())
continue; continue;
@ -194,7 +207,7 @@ void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones,
}; };
bone.tailPosition = toPosition; bone.tailPosition = toPosition;
for (const auto &child: bone.children) { for (const auto &child: bone.children) {
auto &childBone = bones[child]; auto &childBone = (*bones)[child];
childBone.headPosition = toPosition; childBone.headPosition = toPosition;
} }
} }
@ -207,18 +220,70 @@ void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones,
{ {
auto findHeightAboveGroundLevel = map.find("heightAboveGroundLevel"); auto findHeightAboveGroundLevel = map.find("heightAboveGroundLevel");
if (findHeightAboveGroundLevel != map.end()) { if (findHeightAboveGroundLevel != map.end()) {
heightAboveGroundLevel = valueOfKeyInMapOrEmpty(map, "heightAboveGroundLevel").toFloat(); *heightAboveGroundLevel = valueOfKeyInMapOrEmpty(map, "heightAboveGroundLevel").toFloat();
}
} }
} }
} }
updateRigBones(&bones, heightAboveGroundLevel); void PoseDocument::fromParameters(const std::vector<RiggerBone> *rigBones,
} const std::map<QString, std::map<QString, QString>> &parameters)
void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const float heightAboveGroundLevel)
{ {
if (nullptr == rigBones || rigBones->empty()) {
m_riggerBones.clear();
return;
}
if (&m_riggerBones != rigBones)
m_riggerBones = *rigBones;
float heightAboveGroundLevel = 0;
std::vector<RiggerBone> bones = *rigBones;
updateBonesAndHeightAboveGroundLevelFromParameters(&bones,
&heightAboveGroundLevel,
parameters);
reset(); reset();
for (const auto &otherParameters: m_otherFramesParameters) {
float otherHeightAboveGroundLevel = 0;
std::vector<RiggerBone> otherBones = *rigBones;
updateBonesAndHeightAboveGroundLevelFromParameters(&otherBones,
&otherHeightAboveGroundLevel,
otherParameters);
std::map<QString, std::pair<QUuid, QUuid>> boneNameToIdsMap;
QUuid groundPartId;
QUuid bonesPartId;
QUuid groundEdgeId;
parametersToNodes(&otherBones,
otherHeightAboveGroundLevel,
&boneNameToIdsMap,
&groundPartId,
&bonesPartId,
&groundEdgeId,
true);
}
parametersToNodes(&bones,
heightAboveGroundLevel,
&m_boneNameToIdsMap,
&m_groundPartId,
&m_bonesPartId,
&m_groundEdgeId,
false);
emit parametersChanged();
}
void PoseDocument::parametersToNodes(const std::vector<RiggerBone> *rigBones,
const float heightAboveGroundLevel,
std::map<QString, std::pair<QUuid, QUuid>> *boneNameToIdsMap,
QUuid *groundPartId,
QUuid *bonesPartId,
QUuid *groundEdgeId,
bool isOther)
{
if (nullptr == rigBones || rigBones->empty()) { if (nullptr == rigBones || rigBones->empty()) {
return; return;
} }
@ -226,9 +291,9 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
std::set<QUuid> newAddedNodeIds; std::set<QUuid> newAddedNodeIds;
std::set<QUuid> newAddedEdgeIds; std::set<QUuid> newAddedEdgeIds;
m_bonesPartId = QUuid::createUuid(); *bonesPartId = QUuid::createUuid();
auto &bonesPart = partMap[m_bonesPartId]; auto &bonesPart = partMap[*bonesPartId];
bonesPart.id = m_bonesPartId; bonesPart.id = *bonesPartId;
//qDebug() << "rigBones size:" << rigBones->size(); //qDebug() << "rigBones size:" << rigBones->size();
@ -248,7 +313,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
const auto &bone = (*rigBones)[edgePair.first]; const auto &bone = (*rigBones)[edgePair.first];
if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) { if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) {
SkeletonNode node; SkeletonNode node;
node.partId = m_bonesPartId; node.partId = *bonesPartId;
node.id = QUuid::createUuid(); node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius); node.setRadius(m_nodeRadius);
node.x = fromOutcomeX(bone.headPosition.x()); node.x = fromOutcomeX(bone.headPosition.x());
@ -267,7 +332,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
const auto &bone = (*rigBones)[edgePair.second]; const auto &bone = (*rigBones)[edgePair.second];
if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) { if (!bone.name.startsWith("Virtual_") || !m_hideRootAndVirtual) {
SkeletonNode node; SkeletonNode node;
node.partId = m_bonesPartId; node.partId = *bonesPartId;
node.id = QUuid::createUuid(); node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius); node.setRadius(m_nodeRadius);
node.x = fromOutcomeX(bone.headPosition.x()); node.x = fromOutcomeX(bone.headPosition.x());
@ -286,7 +351,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
continue; continue;
SkeletonEdge edge; SkeletonEdge edge;
edge.partId = m_bonesPartId; edge.partId = *bonesPartId;
edge.id = QUuid::createUuid(); edge.id = QUuid::createUuid();
edge.nodeIds.push_back(firstNodeId); edge.nodeIds.push_back(firstNodeId);
edge.nodeIds.push_back(secondNodeId); edge.nodeIds.push_back(secondNodeId);
@ -304,7 +369,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
const QUuid &firstNodeId = boneIndexToHeadNodeIdMap[i]; const QUuid &firstNodeId = boneIndexToHeadNodeIdMap[i];
SkeletonNode node; SkeletonNode node;
node.partId = m_bonesPartId; node.partId = *bonesPartId;
node.id = QUuid::createUuid(); node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius / 2); node.setRadius(m_nodeRadius / 2);
node.x = fromOutcomeX(bone.tailPosition.x()); node.x = fromOutcomeX(bone.tailPosition.x());
@ -312,10 +377,10 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
node.z = fromOutcomeZ(bone.tailPosition.z()); node.z = fromOutcomeZ(bone.tailPosition.z());
nodeMap[node.id] = node; nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id); newAddedNodeIds.insert(node.id);
m_boneNameToIdsMap[bone.name] = {firstNodeId, node.id}; (*boneNameToIdsMap)[bone.name] = {firstNodeId, node.id};
SkeletonEdge edge; SkeletonEdge edge;
edge.partId = m_bonesPartId; edge.partId = *bonesPartId;
edge.id = QUuid::createUuid(); edge.id = QUuid::createUuid();
edge.nodeIds.push_back(firstNodeId); edge.nodeIds.push_back(firstNodeId);
edge.nodeIds.push_back(node.id); edge.nodeIds.push_back(node.id);
@ -328,7 +393,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
continue; continue;
} }
for (const auto &child: bone.children) { for (const auto &child: bone.children) {
m_boneNameToIdsMap[bone.name] = {boneIndexToHeadNodeIdMap[i], boneIndexToHeadNodeIdMap[child]}; (*boneNameToIdsMap)[bone.name] = {boneIndexToHeadNodeIdMap[i], boneIndexToHeadNodeIdMap[child]};
} }
} }
@ -337,9 +402,10 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
nodeMap[findRootNodeId->second].setRadius(m_nodeRadius * 2); nodeMap[findRootNodeId->second].setRadius(m_nodeRadius * 2);
} }
m_groundPartId = QUuid::createUuid(); if (!isOther) {
auto &groundPart = partMap[m_groundPartId]; *groundPartId = QUuid::createUuid();
groundPart.id = m_groundPartId; auto &groundPart = partMap[*groundPartId];
groundPart.id = *groundPartId;
float footBottomY = findFootBottomY(); float footBottomY = findFootBottomY();
float legHeight = findLegHeight(); float legHeight = findLegHeight();
@ -349,7 +415,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
std::pair<QUuid, QUuid> groundNodesPair; std::pair<QUuid, QUuid> groundNodesPair;
{ {
SkeletonNode node; SkeletonNode node;
node.partId = m_groundPartId; node.partId = *groundPartId;
node.id = QUuid::createUuid(); node.id = QUuid::createUuid();
node.setRadius(m_groundPlaneHalfThickness); node.setRadius(m_groundPlaneHalfThickness);
node.x = -100; node.x = -100;
@ -362,7 +428,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
{ {
SkeletonNode node; SkeletonNode node;
node.partId = m_groundPartId; node.partId = *groundPartId;
node.id = QUuid::createUuid(); node.id = QUuid::createUuid();
node.setRadius(m_groundPlaneHalfThickness); node.setRadius(m_groundPlaneHalfThickness);
node.x = 100; node.x = 100;
@ -375,16 +441,24 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
{ {
SkeletonEdge edge; SkeletonEdge edge;
edge.partId = m_groundPartId; edge.partId = *groundPartId;
edge.id = QUuid::createUuid(); edge.id = QUuid::createUuid();
edge.nodeIds.push_back(groundNodesPair.first); edge.nodeIds.push_back(groundNodesPair.first);
edge.nodeIds.push_back(groundNodesPair.second); edge.nodeIds.push_back(groundNodesPair.second);
edgeMap[edge.id] = edge; edgeMap[edge.id] = edge;
m_groundEdgeId = edge.id; *groundEdgeId = edge.id;
newAddedEdgeIds.insert(edge.id); newAddedEdgeIds.insert(edge.id);
nodeMap[groundNodesPair.first].edgeIds.push_back(edge.id); nodeMap[groundNodesPair.first].edgeIds.push_back(edge.id);
nodeMap[groundNodesPair.second].edgeIds.push_back(edge.id); nodeMap[groundNodesPair.second].edgeIds.push_back(edge.id);
} }
}
if (isOther) {
for (const auto &nodeIt: newAddedNodeIds)
m_otherIds.insert(nodeIt);
for (const auto &edgeIt: newAddedEdgeIds)
m_otherIds.insert(edgeIt);
}
for (const auto &nodeIt: newAddedNodeIds) { for (const auto &nodeIt: newAddedNodeIds) {
emit nodeAdded(nodeIt); emit nodeAdded(nodeIt);
@ -392,8 +466,6 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
for (const auto &edgeIt: newAddedEdgeIds) { for (const auto &edgeIt: newAddedEdgeIds) {
emit edgeAdded(edgeIt); emit edgeAdded(edgeIt);
} }
emit parametersChanged();
} }
void PoseDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) void PoseDocument::moveNodeBy(QUuid nodeId, float x, float y, float z)

View File

@ -29,10 +29,12 @@ public:
bool originSettled() const override; bool originSettled() const override;
bool isNodeEditable(QUuid nodeId) const override; bool isNodeEditable(QUuid nodeId) const override;
bool isEdgeEditable(QUuid edgeId) const override; bool isEdgeEditable(QUuid edgeId) const override;
bool isNodeDeactivated(QUuid nodeId) const override;
bool isEdgeDeactivated(QUuid edgeId) const override;
void copyNodes(std::set<QUuid> nodeIdSet) const override; void copyNodes(std::set<QUuid> nodeIdSet) const override;
void updateTurnaround(const QImage &image); void updateTurnaround(const QImage &image);
void updateRigBones(const std::vector<RiggerBone> *rigBones, const float heightAboveGroundLevel=0.0); void updateOtherFramesParameters(const std::vector<std::map<QString, std::map<QString, QString>>> &otherFramesParameters);
void reset(); void reset();
void toParameters(std::map<QString, std::map<QString, QString>> &parameters, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const; void toParameters(std::map<QString, std::map<QString, QString>> &parameters, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const;
@ -61,6 +63,16 @@ private:
float findFootBottomY() const; float findFootBottomY() const;
float findFirstSpineY() const; float findFirstSpineY() const;
float findLegHeight() const; float findLegHeight() const;
void parametersToNodes(const std::vector<RiggerBone> *rigBones,
const float heightAboveGroundLevel,
std::map<QString, std::pair<QUuid, QUuid>> *boneNameToIdsMap,
QUuid *groundPartId,
QUuid *bonesPartId,
QUuid *groundEdgeId,
bool isOther=false);
void updateBonesAndHeightAboveGroundLevelFromParameters(std::vector<RiggerBone> *bones,
float *heightAboveGroundLevel,
const std::map<QString, std::map<QString, QString>> &parameters);
std::map<QString, std::pair<QUuid, QUuid>> m_boneNameToIdsMap; std::map<QString, std::pair<QUuid, QUuid>> m_boneNameToIdsMap;
QUuid m_groundPartId; QUuid m_groundPartId;
@ -69,6 +81,8 @@ private:
std::deque<PoseHistoryItem> m_undoItems; std::deque<PoseHistoryItem> m_undoItems;
std::deque<PoseHistoryItem> m_redoItems; std::deque<PoseHistoryItem> m_redoItems;
std::vector<RiggerBone> m_riggerBones; std::vector<RiggerBone> m_riggerBones;
std::vector<std::map<QString, std::map<QString, QString>>> m_otherFramesParameters;
std::set<QUuid> m_otherIds;
static float fromOutcomeX(float x); static float fromOutcomeX(float x);
static float toOutcomeX(float x); static float toOutcomeX(float x);

View File

@ -18,8 +18,6 @@
#include "shortcuts.h" #include "shortcuts.h"
#include "imageforever.h" #include "imageforever.h"
const float PoseEditWidget::m_frameDuration = 0.042; //(1.0 / 24)
PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) : PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
QDialog(parent), QDialog(parent),
m_document(document), m_document(document),
@ -95,6 +93,18 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() { connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() {
setUnsaveState(); setUnsaveState();
}); });
m_durationEdit = new QDoubleSpinBox();
m_durationEdit->setDecimals(2);
m_durationEdit->setMaximum(60);
m_durationEdit->setMinimum(0);
m_durationEdit->setSingleStep(0.1);
m_durationEdit->setValue(m_duration);
connect(m_durationEdit, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this, [=](double value) {
setDuration((float)value);
});
QPushButton *saveButton = new QPushButton(tr("Save")); QPushButton *saveButton = new QPushButton(tr("Save"));
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save); connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
saveButton->setDefault(true); saveButton->setDefault(true);
@ -112,26 +122,67 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
m_currentFrameSlider = new QSlider(Qt::Horizontal); m_currentFrameSlider = new QSlider(Qt::Horizontal);
m_currentFrameSlider->setRange(0, m_frames.size() - 1); m_currentFrameSlider->setRange(0, m_frames.size() - 1);
m_currentFrameSlider->setValue(m_currentFrame); m_currentFrameSlider->setValue(m_currentFrame);
m_currentFrameSlider->hide(); //m_currentFrameSlider->hide();
connect(m_currentFrameSlider, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), this, [=](int value) { connect(m_currentFrameSlider, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), this, [=](int value) {
setCurrentFrame(value); setCurrentFrame(value);
}); });
connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument); connect(m_document, &Document::resultRigChanged, this, &PoseEditWidget::updatePoseDocument);
QPushButton *moveToFirstFrameButton = new QPushButton(Theme::awesome()->icon(fa::angledoubleleft), "");
connect(moveToFirstFrameButton, &QPushButton::clicked, this, [=]() {
setCurrentFrame(0);
});
QPushButton *moveToPreviousFrameButton = new QPushButton(Theme::awesome()->icon(fa::angleleft), "");
connect(moveToPreviousFrameButton, &QPushButton::clicked, this, [=]() {
if (m_currentFrame > 0)
setCurrentFrame(m_currentFrame - 1);
});
QPushButton *moveToNextFrameButton = new QPushButton(Theme::awesome()->icon(fa::angleright), "");
connect(moveToNextFrameButton, &QPushButton::clicked, this, [=]() {
if (m_currentFrame + 1 < (int)m_frames.size())
setCurrentFrame(m_currentFrame + 1);
});
QPushButton *moveToLastFrameButton = new QPushButton(Theme::awesome()->icon(fa::angledoubleright), "");
connect(moveToLastFrameButton, &QPushButton::clicked, this, [=]() {
if (!m_frames.empty())
setCurrentFrame(m_frames.size() - 1);
});
QPushButton *insertAfterFrameButton = new QPushButton(Theme::awesome()->icon(fa::plus), "");
connect(insertAfterFrameButton, &QPushButton::clicked, this, &PoseEditWidget::insertFrameAfterCurrentFrame);
QPushButton *deleteFrameButton = new QPushButton(Theme::awesome()->icon(fa::trash), "");
connect(deleteFrameButton, &QPushButton::clicked, this, &PoseEditWidget::removeCurrentFrame);
QHBoxLayout *timelineLayout = new QHBoxLayout;
timelineLayout->addWidget(insertAfterFrameButton);
timelineLayout->addWidget(moveToFirstFrameButton);
timelineLayout->addWidget(moveToPreviousFrameButton);
timelineLayout->addWidget(moveToNextFrameButton);
timelineLayout->addWidget(moveToLastFrameButton);
timelineLayout->addWidget(m_framesSettingButton);
timelineLayout->addWidget(m_currentFrameSlider);
timelineLayout->addWidget(deleteFrameButton);
timelineLayout->setStretch(6, 1);
QHBoxLayout *baseInfoLayout = new QHBoxLayout; QHBoxLayout *baseInfoLayout = new QHBoxLayout;
baseInfoLayout->addWidget(new QLabel(tr("Name"))); baseInfoLayout->addWidget(new QLabel(tr("Name")));
baseInfoLayout->addWidget(m_nameEdit); baseInfoLayout->addWidget(m_nameEdit);
baseInfoLayout->addWidget(changeReferenceSheet); baseInfoLayout->addSpacing(10);
baseInfoLayout->addWidget(m_framesSettingButton); baseInfoLayout->addWidget(new QLabel(tr("Duration")));
baseInfoLayout->addWidget(m_currentFrameSlider); baseInfoLayout->addWidget(m_durationEdit);
baseInfoLayout->addStretch(); baseInfoLayout->addStretch();
baseInfoLayout->addWidget(changeReferenceSheet);
baseInfoLayout->addWidget(saveButton); baseInfoLayout->addWidget(saveButton);
baseInfoLayout->setStretch(4, 1);
QVBoxLayout *mainLayout = new QVBoxLayout; QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(paramtersLayout); mainLayout->addLayout(paramtersLayout);
mainLayout->addWidget(Theme::createHorizontalLineWidget()); mainLayout->addWidget(Theme::createHorizontalLineWidget());
mainLayout->addLayout(timelineLayout);
mainLayout->addLayout(baseInfoLayout); mainLayout->addLayout(baseInfoLayout);
setLayout(mainLayout); setLayout(mainLayout);
@ -183,7 +234,8 @@ void PoseEditWidget::showFramesSettingPopup(const QPoint &pos)
void PoseEditWidget::updateFramesSettingButton() void PoseEditWidget::updateFramesSettingButton()
{ {
m_currentFrameSlider->setRange(0, m_frames.size() - 1); m_currentFrameSlider->setRange(0, m_frames.size() - 1);
m_currentFrameSlider->setVisible(m_frames.size() > 1); if (m_currentFrame != m_currentFrameSlider->value())
m_currentFrameSlider->setValue(m_currentFrame);
m_framesSettingButton->setText(tr("Frame: %1 / %2").arg(QString::number(m_currentFrame + 1).rightJustified(2, ' ')).arg(QString::number(m_frames.size()).leftJustified(2, ' '))); m_framesSettingButton->setText(tr("Frame: %1 / %2").arg(QString::number(m_currentFrame + 1).rightJustified(2, ' ')).arg(QString::number(m_frames.size()).leftJustified(2, ' ')));
} }
@ -200,7 +252,7 @@ void PoseEditWidget::syncFrameFromCurrent()
{ {
ensureEnoughFrames(); ensureEnoughFrames();
m_frames[m_currentFrame] = {m_currentAttributes, m_currentParameters}; m_frames[m_currentFrame] = {m_currentAttributes, m_currentParameters};
m_frames[m_currentFrame].first["duration"] = QString::number(m_frameDuration); updateFramesDurations();
} }
void PoseEditWidget::setFrameCount(int count) void PoseEditWidget::setFrameCount(int count)
@ -211,12 +263,33 @@ void PoseEditWidget::setFrameCount(int count)
setUnsaveState(); setUnsaveState();
count = std::max(count, 1); count = std::max(count, 1);
m_frames.resize(count); m_frames.resize(count);
updateFramesDurations();
updateFramesSettingButton(); updateFramesSettingButton();
if (m_currentFrame >= count) { if (m_currentFrame >= count) {
setCurrentFrame(count - 1); setCurrentFrame(count - 1);
} }
} }
void PoseEditWidget::updateFramesDurations()
{
if (m_frames.empty())
return;
float frameDuration = m_duration / m_frames.size();
for (auto &frame: m_frames)
frame.first["duration"] = QString::number(frameDuration);
}
void PoseEditWidget::setDuration(float duration)
{
if (qFuzzyCompare(duration, m_duration))
return;
m_duration = duration;
setUnsaveState();
updateFramesDurations();
}
void PoseEditWidget::setCurrentFrame(int frame) void PoseEditWidget::setCurrentFrame(int frame)
{ {
if (m_currentFrame == frame) if (m_currentFrame == frame)
@ -229,6 +302,43 @@ void PoseEditWidget::setCurrentFrame(int frame)
updatePoseDocument(); updatePoseDocument();
} }
void PoseEditWidget::insertFrameAfterCurrentFrame()
{
int currentFrame = m_currentFrame;
m_frames.resize(m_frames.size() + 1);
updateFramesDurations();
if (-1 != currentFrame) {
for (int index = m_frames.size() - 1; index > currentFrame; --index) {
m_frames[index] = m_frames[index - 1];
}
}
setUnsaveState();
setCurrentFrame(currentFrame + 1);
}
void PoseEditWidget::removeCurrentFrame()
{
if (m_frames.size() <= 1)
return;
int currentFrame = m_currentFrame;
if (-1 != currentFrame) {
for (int index = currentFrame + 1; index < (int)m_frames.size(); ++index) {
m_frames[index - 1] = m_frames[index];
}
m_frames.resize(m_frames.size() - 1);
}
updateFramesDurations();
setUnsaveState();
if (currentFrame - 1 >= 0)
setCurrentFrame(currentFrame - 1);
else if (currentFrame < (int)m_frames.size()) {
m_currentFrame = -1;
setCurrentFrame(currentFrame);
} else
setCurrentFrame(0);
}
void PoseEditWidget::changeTurnaround() void PoseEditWidget::changeTurnaround()
{ {
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(), QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
@ -248,6 +358,13 @@ void PoseEditWidget::changeTurnaround()
void PoseEditWidget::updatePoseDocument() void PoseEditWidget::updatePoseDocument()
{ {
m_otherFramesParameters.clear();
for (int i = 0; i < (int)m_frames.size(); ++i) {
if (m_currentFrame == i)
continue;
m_otherFramesParameters.push_back(m_frames[i].second);
}
m_poseDocument->updateOtherFramesParameters(m_otherFramesParameters);
m_poseDocument->fromParameters(m_document->resultRigBones(), m_currentParameters); m_poseDocument->fromParameters(m_document->resultRigBones(), m_currentParameters);
m_poseDocument->clearHistories(); m_poseDocument->clearHistories();
m_poseDocument->saveHistoryItem(); m_poseDocument->saveHistoryItem();
@ -358,6 +475,14 @@ void PoseEditWidget::setEditPoseFrames(std::vector<std::pair<std::map<QString, Q
m_currentAttributes = frame.first; m_currentAttributes = frame.first;
m_currentParameters = frame.second; m_currentParameters = frame.second;
} }
float totalDuration = 0;
for (const auto &frame: m_frames) {
float frameDuration = valueOfKeyInMapOrEmpty(frame.first, "duration").toFloat();
totalDuration += frameDuration;
}
if (qFuzzyIsNull(totalDuration))
totalDuration = 1.0;
m_durationEdit->setValue(totalDuration);
updatePoseDocument(); updatePoseDocument();
updatePreview(); updatePreview();
updateFramesSettingButton(); updateFramesSettingButton();

View File

@ -5,6 +5,7 @@
#include <QCloseEvent> #include <QCloseEvent>
#include <QLineEdit> #include <QLineEdit>
#include <QSlider> #include <QSlider>
#include <QDoubleSpinBox>
#include "posepreviewmanager.h" #include "posepreviewmanager.h"
#include "document.h" #include "document.h"
#include "modelwidget.h" #include "modelwidget.h"
@ -27,7 +28,7 @@ public:
PoseEditWidget(const Document *document, QWidget *parent=nullptr); PoseEditWidget(const Document *document, QWidget *parent=nullptr);
~PoseEditWidget(); ~PoseEditWidget();
static const float m_frameDuration; float m_duration = 1.0;
public slots: public slots:
void updatePoseDocument(); void updatePoseDocument();
void updatePreview(); void updatePreview();
@ -37,7 +38,10 @@ public slots:
void setEditPoseFrames(std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames); void setEditPoseFrames(std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> frames);
void setEditPoseTurnaroundImageId(QUuid imageId); void setEditPoseTurnaroundImageId(QUuid imageId);
void setCurrentFrame(int frame); void setCurrentFrame(int frame);
void insertFrameAfterCurrentFrame();
void removeCurrentFrame();
void setFrameCount(int count); void setFrameCount(int count);
void setDuration(float duration);
void updateTitle(); void updateTitle();
void save(); void save();
void clearUnsaveState(); void clearUnsaveState();
@ -46,6 +50,7 @@ public slots:
private slots: private slots:
void updateFramesSettingButton(); void updateFramesSettingButton();
void showFramesSettingPopup(const QPoint &pos); void showFramesSettingPopup(const QPoint &pos);
void updateFramesDurations();
protected: protected:
QSize sizeHint() const override; QSize sizeHint() const override;
void closeEvent(QCloseEvent *event) override; void closeEvent(QCloseEvent *event) override;
@ -60,11 +65,13 @@ private:
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> m_frames; std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> m_frames;
std::map<QString, QString> m_currentAttributes; std::map<QString, QString> m_currentAttributes;
std::map<QString, std::map<QString, QString>> m_currentParameters; std::map<QString, std::map<QString, QString>> m_currentParameters;
std::vector<std::map<QString, std::map<QString, QString>>> m_otherFramesParameters;
int m_currentFrame = 0; int m_currentFrame = 0;
QUuid m_poseId; QUuid m_poseId;
bool m_unsaved = false; bool m_unsaved = false;
QUuid m_imageId; QUuid m_imageId;
QLineEdit *m_nameEdit = nullptr; QLineEdit *m_nameEdit = nullptr;
QDoubleSpinBox *m_durationEdit = nullptr;
PoseDocument *m_poseDocument = nullptr; PoseDocument *m_poseDocument = nullptr;
SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr; SkeletonGraphicsWidget *m_poseGraphicsWidget = nullptr;
QPushButton *m_framesSettingButton = nullptr; QPushButton *m_framesSettingButton = nullptr;

View File

@ -246,6 +246,14 @@ public:
virtual bool originSettled() const = 0; virtual bool originSettled() const = 0;
virtual bool isNodeEditable(QUuid nodeId) const = 0; virtual bool isNodeEditable(QUuid nodeId) const = 0;
virtual bool isEdgeEditable(QUuid edgeId) const = 0; virtual bool isEdgeEditable(QUuid edgeId) const = 0;
virtual bool isNodeDeactivated(QUuid nodeId) const
{
return false;
};
virtual bool isEdgeDeactivated(QUuid edgeId) const
{
return false;
};
virtual void copyNodes(std::set<QUuid> nodeIdSet) const = 0; virtual void copyNodes(std::set<QUuid> nodeIdSet) const = 0;
public slots: public slots:

View File

@ -698,11 +698,15 @@ bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event)
QGraphicsItem *item = *it; QGraphicsItem *item = *it;
if (item->data(0) == "node") { if (item->data(0) == "node") {
SkeletonGraphicsNodeItem *nodeItem = (SkeletonGraphicsNodeItem *)item; SkeletonGraphicsNodeItem *nodeItem = (SkeletonGraphicsNodeItem *)item;
if (nodeItem->deactivated())
continue;
QPointF origin = nodeItem->origin(); QPointF origin = nodeItem->origin();
float distance2 = pow(origin.x() - scenePos.x(), 2) + pow(origin.y() - scenePos.y(), 2); float distance2 = pow(origin.x() - scenePos.x(), 2) + pow(origin.y() - scenePos.y(), 2);
itemDistance2MapWithMouse.push_back(std::make_pair(item, distance2)); itemDistance2MapWithMouse.push_back(std::make_pair(item, distance2));
} else if (item->data(0) == "edge") { } else if (item->data(0) == "edge") {
SkeletonGraphicsEdgeItem *edgeItem = (SkeletonGraphicsEdgeItem *)item; SkeletonGraphicsEdgeItem *edgeItem = (SkeletonGraphicsEdgeItem *)item;
if (edgeItem->deactivated())
continue;
if (edgeItem->firstItem() && edgeItem->secondItem()) { if (edgeItem->firstItem() && edgeItem->secondItem()) {
QPointF firstOrigin = edgeItem->firstItem()->origin(); QPointF firstOrigin = edgeItem->firstItem()->origin();
QPointF secondOrigin = edgeItem->secondItem()->origin(); QPointF secondOrigin = edgeItem->secondItem()->origin();
@ -1748,6 +1752,10 @@ void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId)
sideProfileItem->setMarkColor(markColor); sideProfileItem->setMarkColor(markColor);
mainProfileItem->setId(nodeId); mainProfileItem->setId(nodeId);
sideProfileItem->setId(nodeId); sideProfileItem->setId(nodeId);
if (m_document->isNodeDeactivated(nodeId)) {
mainProfileItem->setDeactivated(true);
sideProfileItem->setDeactivated(true);
}
if (m_mainProfileOnly) if (m_mainProfileOnly)
sideProfileItem->hide(); sideProfileItem->hide();
scene()->addItem(mainProfileItem); scene()->addItem(mainProfileItem);
@ -1799,6 +1807,10 @@ void SkeletonGraphicsWidget::edgeAdded(QUuid edgeId)
sideProfileEdgeItem->setId(edgeId); sideProfileEdgeItem->setId(edgeId);
mainProfileEdgeItem->setEndpoints(fromIt->second.first, toIt->second.first); mainProfileEdgeItem->setEndpoints(fromIt->second.first, toIt->second.first);
sideProfileEdgeItem->setEndpoints(fromIt->second.second, toIt->second.second); sideProfileEdgeItem->setEndpoints(fromIt->second.second, toIt->second.second);
if (m_document->isNodeDeactivated(edgeId)) {
mainProfileEdgeItem->setDeactivated(true);
sideProfileEdgeItem->setDeactivated(true);
}
if (m_mainProfileOnly) if (m_mainProfileOnly)
sideProfileEdgeItem->hide(); sideProfileEdgeItem->hide();
scene()->addItem(mainProfileEdgeItem); scene()->addItem(mainProfileEdgeItem);

View File

@ -125,15 +125,17 @@ public:
m_profile(profile), m_profile(profile),
m_hovered(false), m_hovered(false),
m_checked(false), m_checked(false),
m_markColor(Qt::transparent) m_markColor(Qt::transparent),
m_deactivated(false)
{ {
setData(0, "node"); setData(0, "node");
setRadius(32); setRadius(32);
} }
void updateAppearance() void updateAppearance()
{ {
QColor color = Theme::white; QColor color = Qt::gray;
if (!m_deactivated) {
switch (m_profile) switch (m_profile)
{ {
case SkeletonProfile::Unknown: case SkeletonProfile::Unknown:
@ -145,6 +147,7 @@ public:
color = Theme::green; color = Theme::green;
break; break;
} }
}
QColor penColor = color; QColor penColor = color;
penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha); penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha);
@ -222,6 +225,15 @@ public:
m_checked = checked; m_checked = checked;
updateAppearance(); updateAppearance();
} }
void setDeactivated(bool deactivated)
{
m_deactivated = deactivated;
updateAppearance();
}
bool deactivated()
{
return m_deactivated;
}
bool checked() bool checked()
{ {
return m_checked; return m_checked;
@ -236,6 +248,7 @@ private:
bool m_hovered; bool m_hovered;
bool m_checked; bool m_checked;
QColor m_markColor; QColor m_markColor;
bool m_deactivated;
}; };
class SkeletonGraphicsEdgeItem : public QGraphicsPolygonItem class SkeletonGraphicsEdgeItem : public QGraphicsPolygonItem
@ -246,7 +259,8 @@ public:
m_secondItem(nullptr), m_secondItem(nullptr),
m_hovered(false), m_hovered(false),
m_checked(false), m_checked(false),
m_profile(SkeletonProfile::Unknown) m_profile(SkeletonProfile::Unknown),
m_deactivated(false)
{ {
setData(0, "edge"); setData(0, "edge");
} }
@ -282,8 +296,9 @@ public:
polygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1; polygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1;
setPolygon(polygon); setPolygon(polygon);
QColor color = Theme::white; QColor color = Qt::gray;
if (!m_deactivated) {
switch (m_firstItem->profile()) switch (m_firstItem->profile())
{ {
case SkeletonProfile::Unknown: case SkeletonProfile::Unknown:
@ -295,6 +310,7 @@ public:
color = Theme::green; color = Theme::green;
break; break;
} }
}
QColor penColor = color; QColor penColor = color;
penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha); penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha);
@ -324,6 +340,15 @@ public:
m_checked = checked; m_checked = checked;
updateAppearance(); updateAppearance();
} }
void setDeactivated(bool deactivated)
{
m_deactivated = deactivated;
updateAppearance();
}
bool deactivated()
{
return m_deactivated;
}
bool checked() bool checked()
{ {
return m_checked; return m_checked;
@ -340,6 +365,7 @@ private:
bool m_hovered; bool m_hovered;
bool m_checked; bool m_checked;
SkeletonProfile m_profile; SkeletonProfile m_profile;
bool m_deactivated;
}; };
class SkeletonGraphicsWidget : public QGraphicsView class SkeletonGraphicsWidget : public QGraphicsView