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>
fornclake <https://www.reddit.com/user/fornclake>
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>
[![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
----------

View File

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

View File

@ -29,10 +29,12 @@ public:
bool originSettled() const override;
bool isNodeEditable(QUuid nodeId) 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 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 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 findFirstSpineY() 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;
QUuid m_groundPartId;
@ -69,6 +81,8 @@ private:
std::deque<PoseHistoryItem> m_undoItems;
std::deque<PoseHistoryItem> m_redoItems;
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 toOutcomeX(float x);

View File

@ -18,8 +18,6 @@
#include "shortcuts.h"
#include "imageforever.h"
const float PoseEditWidget::m_frameDuration = 0.042; //(1.0 / 24)
PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
QDialog(parent),
m_document(document),
@ -95,6 +93,18 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() {
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"));
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
saveButton->setDefault(true);
@ -112,26 +122,67 @@ PoseEditWidget::PoseEditWidget(const Document *document, QWidget *parent) :
m_currentFrameSlider = new QSlider(Qt::Horizontal);
m_currentFrameSlider->setRange(0, m_frames.size() - 1);
m_currentFrameSlider->setValue(m_currentFrame);
m_currentFrameSlider->hide();
//m_currentFrameSlider->hide();
connect(m_currentFrameSlider, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), this, [=](int value) {
setCurrentFrame(value);
});
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;
baseInfoLayout->addWidget(new QLabel(tr("Name")));
baseInfoLayout->addWidget(m_nameEdit);
baseInfoLayout->addWidget(changeReferenceSheet);
baseInfoLayout->addWidget(m_framesSettingButton);
baseInfoLayout->addWidget(m_currentFrameSlider);
baseInfoLayout->addSpacing(10);
baseInfoLayout->addWidget(new QLabel(tr("Duration")));
baseInfoLayout->addWidget(m_durationEdit);
baseInfoLayout->addStretch();
baseInfoLayout->addWidget(changeReferenceSheet);
baseInfoLayout->addWidget(saveButton);
baseInfoLayout->setStretch(4, 1);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(paramtersLayout);
mainLayout->addWidget(Theme::createHorizontalLineWidget());
mainLayout->addLayout(timelineLayout);
mainLayout->addLayout(baseInfoLayout);
setLayout(mainLayout);
@ -183,7 +234,8 @@ void PoseEditWidget::showFramesSettingPopup(const QPoint &pos)
void PoseEditWidget::updateFramesSettingButton()
{
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, ' ')));
}
@ -200,7 +252,7 @@ void PoseEditWidget::syncFrameFromCurrent()
{
ensureEnoughFrames();
m_frames[m_currentFrame] = {m_currentAttributes, m_currentParameters};
m_frames[m_currentFrame].first["duration"] = QString::number(m_frameDuration);
updateFramesDurations();
}
void PoseEditWidget::setFrameCount(int count)
@ -211,12 +263,33 @@ void PoseEditWidget::setFrameCount(int count)
setUnsaveState();
count = std::max(count, 1);
m_frames.resize(count);
updateFramesDurations();
updateFramesSettingButton();
if (m_currentFrame >= count) {
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)
{
if (m_currentFrame == frame)
@ -229,6 +302,43 @@ void PoseEditWidget::setCurrentFrame(int frame)
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()
{
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
@ -248,6 +358,13 @@ void PoseEditWidget::changeTurnaround()
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->clearHistories();
m_poseDocument->saveHistoryItem();
@ -358,6 +475,14 @@ void PoseEditWidget::setEditPoseFrames(std::vector<std::pair<std::map<QString, Q
m_currentAttributes = frame.first;
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();
updatePreview();
updateFramesSettingButton();

View File

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

View File

@ -246,6 +246,14 @@ public:
virtual bool originSettled() const = 0;
virtual bool isNodeEditable(QUuid nodeId) 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;
public slots:

View File

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

View File

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