Support onion skin in pose editor
Also add more buttons to speed up the pose editing. Thanks @Toshiwoz for proposing this feature.master
parent
8c0156bc7a
commit
26353d530a
|
@ -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>
|
|
@ -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
|
||||
----------
|
||||
|
|
|
@ -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,
|
||||
void PoseDocument::updateBonesAndHeightAboveGroundLevelFromParameters(std::vector<RiggerBone> *bones,
|
||||
float *heightAboveGroundLevel,
|
||||
const std::map<QString, std::map<QString, QString>> ¶meters)
|
||||
{
|
||||
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>> ¶meters)
|
||||
{
|
||||
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,9 +402,10 @@ 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();
|
||||
|
@ -349,7 +415,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
|
|||
std::pair<QUuid, QUuid> groundNodesPair;
|
||||
{
|
||||
SkeletonNode node;
|
||||
node.partId = m_groundPartId;
|
||||
node.partId = *groundPartId;
|
||||
node.id = QUuid::createUuid();
|
||||
node.setRadius(m_groundPlaneHalfThickness);
|
||||
node.x = -100;
|
||||
|
@ -362,7 +428,7 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
|
|||
|
||||
{
|
||||
SkeletonNode node;
|
||||
node.partId = m_groundPartId;
|
||||
node.partId = *groundPartId;
|
||||
node.id = QUuid::createUuid();
|
||||
node.setRadius(m_groundPlaneHalfThickness);
|
||||
node.x = 100;
|
||||
|
@ -375,16 +441,24 @@ void PoseDocument::updateRigBones(const std::vector<RiggerBone> *rigBones, const
|
|||
|
||||
{
|
||||
SkeletonEdge edge;
|
||||
edge.partId = m_groundPartId;
|
||||
edge.partId = *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;
|
||||
*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) {
|
||||
emit nodeAdded(nodeIt);
|
||||
|
@ -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)
|
||||
|
|
|
@ -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>> ¶meters, 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>> ¶meters);
|
||||
|
||||
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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -125,15 +125,17 @@ 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;
|
||||
|
||||
if (!m_deactivated) {
|
||||
switch (m_profile)
|
||||
{
|
||||
case SkeletonProfile::Unknown:
|
||||
|
@ -145,6 +147,7 @@ public:
|
|||
color = Theme::green;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QColor penColor = color;
|
||||
penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha);
|
||||
|
@ -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,8 +296,9 @@ public:
|
|||
polygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1;
|
||||
setPolygon(polygon);
|
||||
|
||||
QColor color = Theme::white;
|
||||
QColor color = Qt::gray;
|
||||
|
||||
if (!m_deactivated) {
|
||||
switch (m_firstItem->profile())
|
||||
{
|
||||
case SkeletonProfile::Unknown:
|
||||
|
@ -295,6 +310,7 @@ public:
|
|||
color = Theme::green;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
QColor penColor = color;
|
||||
penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha);
|
||||
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue