From 26353d530ac97fb2a5e68421c640f30b9e2a2072 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sun, 3 Mar 2019 09:35:13 +0930 Subject: [PATCH] Support onion skin in pose editor Also add more buttons to speed up the pose editing. Thanks @Toshiwoz for proposing this feature. --- CONTRIBUTORS | 3 +- README.md | 2 +- src/posedocument.cpp | 228 ++++++++++++++++++++++----------- src/posedocument.h | 16 ++- src/poseeditwidget.cpp | 143 +++++++++++++++++++-- src/poseeditwidget.h | 9 +- src/skeletondocument.h | 8 ++ src/skeletongraphicswidget.cpp | 12 ++ src/skeletongraphicswidget.h | 74 +++++++---- 9 files changed, 380 insertions(+), 115 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f2505be1..c312eff6 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -24,4 +24,5 @@ justStand Ruben Niculcea boynet fornclake -bvanevery \ No newline at end of file +bvanevery +Toshio Araki \ No newline at end of file diff --git a/README.md b/README.md index b4cb868e..f220ac46 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -[![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 ---------- diff --git a/src/posedocument.cpp b/src/posedocument.cpp index 18f367ec..1288b4dd 100644 --- a/src/posedocument.cpp +++ b/src/posedocument.cpp @@ -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 nodeIdSet) const { std::map> parameters; @@ -129,11 +145,17 @@ void PoseDocument::updateTurnaround(const QImage &image) emit turnaroundChanged(); } +void PoseDocument::updateOtherFramesParameters(const std::vector>> &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 *rigBones, - const std::map> ¶meters) +void PoseDocument::updateBonesAndHeightAboveGroundLevelFromParameters(std::vector *bones, + float *heightAboveGroundLevel, + const std::map> ¶meters) { - if (nullptr == rigBones || rigBones->empty()) { - m_riggerBones.clear(); - return; - } - - if (&m_riggerBones != rigBones) - m_riggerBones = *rigBones; - - float heightAboveGroundLevel = 0; - std::vector 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 *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 *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 *rigBones, const float heightAboveGroundLevel) +void PoseDocument::fromParameters(const std::vector *rigBones, + const std::map> ¶meters) { + if (nullptr == rigBones || rigBones->empty()) { + m_riggerBones.clear(); + return; + } + + if (&m_riggerBones != rigBones) + m_riggerBones = *rigBones; + + float heightAboveGroundLevel = 0; + std::vector bones = *rigBones; + updateBonesAndHeightAboveGroundLevelFromParameters(&bones, + &heightAboveGroundLevel, + parameters); + reset(); + for (const auto &otherParameters: m_otherFramesParameters) { + float otherHeightAboveGroundLevel = 0; + std::vector otherBones = *rigBones; + updateBonesAndHeightAboveGroundLevelFromParameters(&otherBones, + &otherHeightAboveGroundLevel, + otherParameters); + + std::map> 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 *rigBones, + const float heightAboveGroundLevel, + std::map> *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 *rigBones, const std::set newAddedNodeIds; std::set 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 *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 *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 *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 *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 *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 *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 *rigBones, const nodeMap[findRootNodeId->second].setRadius(m_nodeRadius * 2); } - m_groundPartId = QUuid::createUuid(); - auto &groundPart = partMap[m_groundPartId]; - groundPart.id = m_groundPartId; - - float footBottomY = findFootBottomY(); - float legHeight = findLegHeight(); - float myHeightAboveGroundLevel = heightAboveGroundLevel * legHeight; - float groundNodeY = footBottomY + m_groundPlaneHalfThickness + myHeightAboveGroundLevel; - - std::pair 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; + 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; + + std::pair 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 *rigBones, const for (const auto &edgeIt: newAddedEdgeIds) { emit edgeAdded(edgeIt); } - - emit parametersChanged(); } void PoseDocument::moveNodeBy(QUuid nodeId, float x, float y, float z) diff --git a/src/posedocument.h b/src/posedocument.h index d723110c..63976d7f 100644 --- a/src/posedocument.h +++ b/src/posedocument.h @@ -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 nodeIdSet) const override; void updateTurnaround(const QImage &image); - void updateRigBones(const std::vector *rigBones, const float heightAboveGroundLevel=0.0); + void updateOtherFramesParameters(const std::vector>> &otherFramesParameters); void reset(); void toParameters(std::map> ¶meters, const std::set &limitNodeIds=std::set()) const; @@ -61,6 +63,16 @@ private: float findFootBottomY() const; float findFirstSpineY() const; float findLegHeight() const; + void parametersToNodes(const std::vector *rigBones, + const float heightAboveGroundLevel, + std::map> *boneNameToIdsMap, + QUuid *groundPartId, + QUuid *bonesPartId, + QUuid *groundEdgeId, + bool isOther=false); + void updateBonesAndHeightAboveGroundLevelFromParameters(std::vector *bones, + float *heightAboveGroundLevel, + const std::map> ¶meters); std::map> m_boneNameToIdsMap; QUuid m_groundPartId; @@ -69,6 +81,8 @@ private: std::deque m_undoItems; std::deque m_redoItems; std::vector m_riggerBones; + std::vector>> m_otherFramesParameters; + std::set m_otherIds; static float fromOutcomeX(float x); static float toOutcomeX(float x); diff --git a/src/poseeditwidget.cpp b/src/poseeditwidget.cpp index 78141aca..6ec3baf3 100644 --- a/src/poseeditwidget.cpp +++ b/src/poseeditwidget.cpp @@ -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(&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(&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::vectorsetValue(totalDuration); updatePoseDocument(); updatePreview(); updateFramesSettingButton(); diff --git a/src/poseeditwidget.h b/src/poseeditwidget.h index 8b31b306..ce63d0d5 100644 --- a/src/poseeditwidget.h +++ b/src/poseeditwidget.h @@ -5,6 +5,7 @@ #include #include #include +#include #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::map>>> 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::map>>> m_frames; std::map m_currentAttributes; std::map> m_currentParameters; + std::vector>> 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; diff --git a/src/skeletondocument.h b/src/skeletondocument.h index e37db897..34dadb7f 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -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 nodeIdSet) const = 0; public slots: diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index f8b1c4ae..11df0eaa 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -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); diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index ce683a66..40ee6501 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -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