Add poses list view

Added Add, modify, delete, copy, and paste poses. The paste menu for nodes not show up also got fixed in this commit.
master
Jeremy Hu 2018-09-21 15:10:18 +08:00
parent f163522aa2
commit e22a6de6cb
34 changed files with 1451 additions and 245 deletions

View File

@ -179,8 +179,20 @@ HEADERS += src/posemeshcreator.h
SOURCES += src/posepreviewmanager.cpp SOURCES += src/posepreviewmanager.cpp
HEADERS += src/posepreviewmanager.h HEADERS += src/posepreviewmanager.h
SOURCES += src/tetrapodposeeditwidget.cpp SOURCES += src/poseeditwidget.cpp
HEADERS += src/tetrapodposeeditwidget.h HEADERS += src/poseeditwidget.h
SOURCES += src/poselistwidget.cpp
HEADERS += src/poselistwidget.h
SOURCES += src/posemanagewidget.cpp
HEADERS += src/posemanagewidget.h
SOURCES += src/posepreviewsgenerator.cpp
HEADERS += src/posepreviewsgenerator.h
SOURCES += src/posewidget.cpp
HEADERS += src/posewidget.h
SOURCES += src/main.cpp SOURCES += src/main.cpp

View File

@ -3,6 +3,7 @@
#include <QOpenGLFunctions> #include <QOpenGLFunctions>
#include "aboutwidget.h" #include "aboutwidget.h"
#include "version.h" #include "version.h"
#include "dust3dutil.h"
AboutWidget::AboutWidget() AboutWidget::AboutWidget()
{ {
@ -16,5 +17,5 @@ AboutWidget::AboutWidget()
setLayout(mainLayout); setLayout(mainLayout);
setFixedSize(QSize(350, 75)); setFixedSize(QSize(350, 75));
setWindowTitle(APP_NAME); setWindowTitle(unifiedWindowTitle(tr("About")));
} }

View File

@ -1,8 +1,8 @@
#ifndef ABOUT_WIDGET_H #ifndef ABOUT_WIDGET_H
#define ABOUT_WIDGET_H #define ABOUT_WIDGET_H
#include <QWidget> #include <QDialog>
class AboutWidget : public QWidget class AboutWidget : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:

View File

@ -1,5 +1,6 @@
#include <cmath> #include <cmath>
#include "dust3dutil.h" #include "dust3dutil.h"
#include "version.h"
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key) QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
{ {
@ -51,3 +52,7 @@ QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal)
return line - verticalOffset; return line - verticalOffset;
} }
QString unifiedWindowTitle(const QString &text)
{
return text + QObject::tr(" - ") + APP_NAME;
}

View File

@ -17,5 +17,6 @@ void qNormalizeAngle(int &angle);
QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1); QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1);
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal); float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal); QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
QString unifiedWindowTitle(const QString &text);
#endif #endif

View File

@ -7,9 +7,10 @@
#include "aboutwidget.h" #include "aboutwidget.h"
#include "version.h" #include "version.h"
#include "theme.h" #include "theme.h"
#include "dust3dutil.h"
ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *parent) : ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *parent) :
QWidget(parent), QDialog(parent),
m_document(document), m_document(document),
m_previewLabel(nullptr), m_previewLabel(nullptr),
m_spinnerWidget(nullptr) m_spinnerWidget(nullptr)
@ -33,6 +34,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
m_saveButton = new QPushButton(tr("Save")); m_saveButton = new QPushButton(tr("Save"));
connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save); connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save);
m_saveButton->hide(); m_saveButton->hide();
m_saveButton->setDefault(true);
QComboBox *exportFormatSelectBox = new QComboBox; QComboBox *exportFormatSelectBox = new QComboBox;
exportFormatSelectBox->addItem(tr("glTF")); exportFormatSelectBox->addItem(tr("glTF"));
@ -59,11 +61,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
renderLayout->setContentsMargins(20, 0, 20, 0); renderLayout->setContentsMargins(20, 0, 20, 0);
renderLayout->addWidget(m_textureRenderWidget); renderLayout->addWidget(m_textureRenderWidget);
QWidget *hrLightWidget = new QWidget; QWidget *hrLightWidget = Theme::createHorizontalLineWidget();
hrLightWidget->setFixedHeight(1);
hrLightWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
hrLightWidget->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *topLayout = new QHBoxLayout; QHBoxLayout *topLayout = new QHBoxLayout;
topLayout->setSpacing(0); topLayout->setSpacing(0);
@ -86,7 +84,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
m_spinnerWidget->setNumberOfLines(12); m_spinnerWidget->setNumberOfLines(12);
m_spinnerWidget->hide(); m_spinnerWidget->hide();
setWindowTitle(tr("Export") + tr(" - ") + APP_NAME); setWindowTitle(unifiedWindowTitle(tr("Export")));
emit updateTexturePreview(); emit updateTexturePreview();
} }

View File

@ -1,6 +1,6 @@
#ifndef EXPORT_PREVIEW_WIDGET_H #ifndef EXPORT_PREVIEW_WIDGET_H
#define EXPORT_PREVIEW_WIDGET_H #define EXPORT_PREVIEW_WIDGET_H
#include <QWidget> #include <QDialog>
#include <QImage> #include <QImage>
#include <QLabel> #include <QLabel>
#include <QPushButton> #include <QPushButton>
@ -9,7 +9,7 @@
#include "waitingspinnerwidget.h" #include "waitingspinnerwidget.h"
#include "skeletondocument.h" #include "skeletondocument.h"
class ExportPreviewWidget : public QWidget class ExportPreviewWidget : public QDialog
{ {
Q_OBJECT Q_OBJECT
signals: signals:

View File

@ -12,6 +12,7 @@
#include <QCloseEvent> #include <QCloseEvent>
#include <QKeyEvent> #include <QKeyEvent>
#include "version.h" #include "version.h"
#include "dust3dutil.h"
LogBrowserDialog::LogBrowserDialog(QWidget *parent) : LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
QDialog(parent) QDialog(parent)
@ -40,7 +41,7 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
resize(400, 300); resize(400, 300);
setWindowTitle(tr("Debug") + tr(" - ") + APP_NAME); setWindowTitle(unifiedWindowTitle(tr("Debug")));
hide(); hide();
} }

View File

@ -434,7 +434,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
auto findCachedMesh = m_cacheContext->componentCombinableMeshs.find(componentId); auto findCachedMesh = m_cacheContext->componentCombinableMeshs.find(componentId);
if (findCachedMesh != m_cacheContext->componentCombinableMeshs.end() && if (findCachedMesh != m_cacheContext->componentCombinableMeshs.end() &&
nullptr != findCachedMesh->second) { nullptr != findCachedMesh->second) {
qDebug() << "Component mesh cache used:" << componentId; //qDebug() << "Component mesh cache used:" << componentId;
return cloneCombinableMesh(findCachedMesh->second); return cloneCombinableMesh(findCachedMesh->second);
} }
} }
@ -519,7 +519,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
} }
} }
if (seamVerticesNum > 0) { if (seamVerticesNum > 0) {
qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum; //qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum); meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum);
} }
delete[] seamVerticesIndicies; delete[] seamVerticesIndicies;

View File

@ -1,21 +1,32 @@
#include <QVBoxLayout> #include <QVBoxLayout>
#include <QHBoxLayout>
#include <QFormLayout> #include <QFormLayout>
#include <QGridLayout> #include <QGridLayout>
#include <QMenu> #include <QMenu>
#include <QWidgetAction> #include <QWidgetAction>
#include <QLineEdit>
#include <QMessageBox>
#include "theme.h" #include "theme.h"
#include "tetrapodposeeditwidget.h" #include "poseeditwidget.h"
#include "floatnumberwidget.h" #include "floatnumberwidget.h"
#include "version.h"
TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent) : PoseEditWidget::PoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
QWidget(parent), QDialog(parent),
m_document(document) m_document(document)
{ {
m_posePreviewManager = new PosePreviewManager(); m_posePreviewManager = new PosePreviewManager();
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() { connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
if (m_closed) {
close();
return;
}
if (m_isPreviewDirty) if (m_isPreviewDirty)
updatePreview(); updatePreview();
}); });
connect(m_posePreviewManager, &PosePreviewManager::resultPreviewMeshChanged, [=]() {
m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh());
});
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons; std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll); buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll);
@ -61,36 +72,117 @@ TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document,
QFont buttonFont; QFont buttonFont;
buttonFont.setWeight(QFont::Light); buttonFont.setWeight(QFont::Light);
buttonFont.setPixelSize(7); buttonFont.setPixelSize(9);
buttonFont.setBold(false); buttonFont.setBold(false);
for (const auto &item: buttons) { for (const auto &item: buttons) {
QString boneName = item.first; QString boneName = item.first;
QPushButton *buttonWidget = std::get<0>(item.second); QPushButton *buttonWidget = std::get<0>(item.second);
PopupWidgetType widgetType = std::get<1>(item.second); PopupWidgetType widgetType = std::get<1>(item.second);
buttonWidget->setFont(buttonFont); buttonWidget->setFont(buttonFont);
buttonWidget->setMaximumWidth(45); buttonWidget->setMaximumWidth(55);
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() { connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos())); emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
}); });
} }
QVBoxLayout *layout = new QVBoxLayout; m_previewWidget = new ModelWidget(this);
layout->addLayout(marksContainerLayout); m_previewWidget->setMinimumSize(128, 128);
layout->addLayout(lowerMarksContainerLayout); m_previewWidget->resize(384, 384);
layout->addStretch(); m_previewWidget->move(-64, -64+22);
setLayout(layout); QVBoxLayout *markButtonsLayout = new QVBoxLayout;
markButtonsLayout->addStretch();
markButtonsLayout->addLayout(marksContainerLayout);
markButtonsLayout->addLayout(lowerMarksContainerLayout);
markButtonsLayout->addStretch();
connect(m_document, &SkeletonDocument::resultRigChanged, this, &TetrapodPoseEditWidget::updatePreview); QHBoxLayout *paramtersLayout = new QHBoxLayout;
paramtersLayout->setContentsMargins(256, 0, 0, 0);
paramtersLayout->addStretch();
paramtersLayout->addLayout(markButtonsLayout);
paramtersLayout->addSpacing(20);
m_nameEdit = new QLineEdit;
connect(m_nameEdit, &QLineEdit::textChanged, this, [=]() {
m_unsaved = true;
updateTitle();
});
QPushButton *saveButton = new QPushButton(tr("Save"));
connect(saveButton, &QPushButton::clicked, this, &PoseEditWidget::save);
saveButton->setDefault(true);
QHBoxLayout *baseInfoLayout = new QHBoxLayout;
baseInfoLayout->addWidget(new QLabel(tr("Name")));
baseInfoLayout->addWidget(m_nameEdit);
baseInfoLayout->addStretch();
baseInfoLayout->addWidget(saveButton);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(paramtersLayout);
mainLayout->addWidget(Theme::createHorizontalLineWidget());
mainLayout->addLayout(baseInfoLayout);
setLayout(mainLayout);
connect(m_document, &SkeletonDocument::resultRigChanged, this, &PoseEditWidget::updatePreview);
connect(this, &PoseEditWidget::parametersAdjusted, this, &PoseEditWidget::updatePreview);
connect(this, &PoseEditWidget::parametersAdjusted, [=]() {
m_unsaved = true;
updateTitle();
});
connect(this, &PoseEditWidget::addPose, m_document, &SkeletonDocument::addPose);
connect(this, &PoseEditWidget::renamePose, m_document, &SkeletonDocument::renamePose);
connect(this, &PoseEditWidget::setPoseParameters, m_document, &SkeletonDocument::setPoseParameters);
updatePreview();
updateTitle();
} }
TetrapodPoseEditWidget::~TetrapodPoseEditWidget() void PoseEditWidget::reject()
{
close();
}
void PoseEditWidget::closeEvent(QCloseEvent *event)
{
if (m_unsaved && !m_closed) {
QMessageBox::StandardButton answer = QMessageBox::question(this,
APP_NAME,
tr("Do you really want to close while there are unsaved changes?"),
QMessageBox::Yes | QMessageBox::No);
if (answer != QMessageBox::Yes) {
event->ignore();
return;
}
}
m_closed = true;
hide();
if (m_openedMenuCount > 0) {
event->ignore();
return;
}
if (m_posePreviewManager->isRendering()) {
event->ignore();
return;
}
event->accept();
}
QSize PoseEditWidget::sizeHint() const
{
return QSize(0, 350);
}
PoseEditWidget::~PoseEditWidget()
{ {
delete m_posePreviewManager; delete m_posePreviewManager;
} }
void TetrapodPoseEditWidget::updatePreview() void PoseEditWidget::updatePreview()
{ {
if (m_closed)
return;
if (m_posePreviewManager->isRendering()) { if (m_posePreviewManager->isRendering()) {
m_isPreviewDirty = true; m_isPreviewDirty = true;
return; return;
@ -105,14 +197,37 @@ void TetrapodPoseEditWidget::updatePreview()
return; return;
} }
delete m_poser; TetrapodPoser *poser = new TetrapodPoser(*rigBones);
m_poser = new TetrapodPoser(*rigBones); poser->parameters() = m_parameters;
m_poser->parameters() = m_parameters; poser->commit();
m_poser->commit(); m_posePreviewManager->postUpdate(*poser, m_document->currentRiggedResultContext(), *rigWeights);
m_posePreviewManager->postUpdate(*m_poser, m_document->currentRiggedResultContext(), *rigWeights); delete poser;
} }
void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos) void PoseEditWidget::setEditPoseId(QUuid poseId)
{
if (m_poseId == poseId)
return;
m_poseId = poseId;
updateTitle();
}
void PoseEditWidget::updateTitle()
{
if (m_poseId.isNull()) {
setWindowTitle(unifiedWindowTitle(tr("New") + (m_unsaved ? "*" : "")));
return;
}
const SkeletonPose *pose = m_document->findPose(m_poseId);
if (nullptr == pose) {
qDebug() << "Find pose failed:" << m_poseId;
return;
}
setWindowTitle(unifiedWindowTitle(pose->name + (m_unsaved ? "*" : "")));
}
void PoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos)
{ {
QMenu popupMenu; QMenu popupMenu;
@ -125,13 +240,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
pitchWidget->setItemName(tr("Pitch")); pitchWidget->setItemName(tr("Pitch"));
pitchWidget->setRange(-180, 180); pitchWidget->setRange(-180, 180);
pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat()); pitchWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "pitch").toFloat());
connect(pitchWidget, &FloatNumberWidget::valueChanged, [=](float value) { connect(pitchWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["pitch"] = QString::number(value); m_parameters[boneName]["pitch"] = QString::number(value);
updatePreview(); emit parametersAdjusted();
}); });
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser)); QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(pitchEraser); Theme::initAwesomeMiniButton(pitchEraser);
connect(pitchEraser, &QPushButton::clicked, [=]() { connect(pitchEraser, &QPushButton::clicked, this, [=]() {
pitchWidget->setValue(0.0); pitchWidget->setValue(0.0);
}); });
QHBoxLayout *pitchLayout = new QHBoxLayout; QHBoxLayout *pitchLayout = new QHBoxLayout;
@ -143,13 +258,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
yawWidget->setItemName(tr("Yaw")); yawWidget->setItemName(tr("Yaw"));
yawWidget->setRange(-180, 180); yawWidget->setRange(-180, 180);
yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat()); yawWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "yaw").toFloat());
connect(yawWidget, &FloatNumberWidget::valueChanged, [=](float value) { connect(yawWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["yaw"] = QString::number(value); m_parameters[boneName]["yaw"] = QString::number(value);
updatePreview(); emit parametersAdjusted();
}); });
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser)); QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(yawEraser); Theme::initAwesomeMiniButton(yawEraser);
connect(yawEraser, &QPushButton::clicked, [=]() { connect(yawEraser, &QPushButton::clicked, this, [=]() {
yawWidget->setValue(0.0); yawWidget->setValue(0.0);
}); });
QHBoxLayout *yawLayout = new QHBoxLayout; QHBoxLayout *yawLayout = new QHBoxLayout;
@ -161,13 +276,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
rollWidget->setItemName(tr("Roll")); rollWidget->setItemName(tr("Roll"));
rollWidget->setRange(-180, 180); rollWidget->setRange(-180, 180);
rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat()); rollWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "roll").toFloat());
connect(rollWidget, &FloatNumberWidget::valueChanged, [=](float value) { connect(rollWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["roll"] = QString::number(value); m_parameters[boneName]["roll"] = QString::number(value);
updatePreview(); emit parametersAdjusted();
}); });
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser)); QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(rollEraser); Theme::initAwesomeMiniButton(rollEraser);
connect(rollEraser, &QPushButton::clicked, [=]() { connect(rollEraser, &QPushButton::clicked, this, [=]() {
rollWidget->setValue(0.0); rollWidget->setValue(0.0);
}); });
QHBoxLayout *rollLayout = new QHBoxLayout; QHBoxLayout *rollLayout = new QHBoxLayout;
@ -179,13 +294,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
intersectionWidget->setItemName(tr("Intersection")); intersectionWidget->setItemName(tr("Intersection"));
intersectionWidget->setRange(-180, 180); intersectionWidget->setRange(-180, 180);
intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat()); intersectionWidget->setValue(valueOfKeyInMapOrEmpty(m_parameters[boneName], "intersection").toFloat());
connect(intersectionWidget, &FloatNumberWidget::valueChanged, [=](float value) { connect(intersectionWidget, &FloatNumberWidget::valueChanged, this, [=](float value) {
m_parameters[boneName]["intersection"] = QString::number(value); m_parameters[boneName]["intersection"] = QString::number(value);
updatePreview(); emit parametersAdjusted();
}); });
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser)); QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
Theme::initAwesomeMiniButton(intersectionEraser); Theme::initAwesomeMiniButton(intersectionEraser);
connect(intersectionEraser, &QPushButton::clicked, [=]() { connect(intersectionEraser, &QPushButton::clicked, this, [=]() {
intersectionWidget->setValue(0.0); intersectionWidget->setValue(0.0);
}); });
QHBoxLayout *intersectionLayout = new QHBoxLayout; QHBoxLayout *intersectionLayout = new QHBoxLayout;
@ -201,5 +316,40 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
popupMenu.addAction(&action); popupMenu.addAction(&action);
m_openedMenuCount++;
popupMenu.exec(mapToGlobal(pos)); popupMenu.exec(mapToGlobal(pos));
m_openedMenuCount--;
if (m_closed)
close();
}
void PoseEditWidget::setEditPoseName(QString name)
{
m_nameEdit->setText(name);
updateTitle();
}
void PoseEditWidget::setEditParameters(std::map<QString, std::map<QString, QString>> parameters)
{
m_parameters = parameters;
updatePreview();
}
void PoseEditWidget::clearUnsaveState()
{
m_unsaved = false;
updateTitle();
}
void PoseEditWidget::save()
{
if (m_poseId.isNull()) {
emit addPose(m_nameEdit->text(), m_parameters);
} else if (m_unsaved) {
m_unsaved = false;
emit renamePose(m_poseId, m_nameEdit->text());
emit setPoseParameters(m_poseId, m_parameters);
}
close();
} }

56
src/poseeditwidget.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef POSE_EDIT_WIDGET_H
#define POSE_EDIT_WIDGET_H
#include <QDialog>
#include <map>
#include <QCloseEvent>
#include <QLineEdit>
#include "posepreviewmanager.h"
#include "tetrapodposer.h"
#include "skeletondocument.h"
#include "modelwidget.h"
enum class PopupWidgetType
{
PitchYawRoll,
Intersection
};
class PoseEditWidget : public QDialog
{
Q_OBJECT
signals:
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
void removePose(QUuid poseId);
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters);
void renamePose(QUuid poseId, QString name);
void parametersAdjusted();
public:
PoseEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
~PoseEditWidget();
public slots:
void updatePreview();
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
void setEditPoseId(QUuid poseId);
void setEditPoseName(QString name);
void setEditParameters(std::map<QString, std::map<QString, QString>> parameters);
void updateTitle();
void save();
void clearUnsaveState();
protected:
QSize sizeHint() const override;
void closeEvent(QCloseEvent *event) override;
void reject() override;
private:
const SkeletonDocument *m_document = nullptr;
PosePreviewManager *m_posePreviewManager = nullptr;
ModelWidget *m_previewWidget = nullptr;
bool m_isPreviewDirty = false;
bool m_closed = false;
std::map<QString, std::map<QString, QString>> m_parameters;
size_t m_openedMenuCount = 0;
QUuid m_poseId;
bool m_unsaved = false;
QLineEdit *m_nameEdit = nullptr;
};
#endif

316
src/poselistwidget.cpp Normal file
View File

@ -0,0 +1,316 @@
#include <QGuiApplication>
#include <QMenu>
#include <QXmlStreamWriter>
#include <QClipboard>
#include <QApplication>
#include "skeletonxml.h"
#include "poselistwidget.h"
PoseListWidget::PoseListWidget(const SkeletonDocument *document, QWidget *parent) :
QTreeWidget(parent),
m_document(document)
{
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setFocusPolicy(Qt::NoFocus);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setAutoScroll(false);
setHeaderHidden(true);
QPalette palette = this->palette();
palette.setColor(QPalette::Window, Qt::transparent);
palette.setColor(QPalette::Base, Qt::transparent);
setPalette(palette);
setStyleSheet("QTreeView {qproperty-indentation: 0;}");
setContentsMargins(0, 0, 0, 0);
connect(document, &SkeletonDocument::poseListChanged, this, &PoseListWidget::reload);
connect(document, &SkeletonDocument::cleanup, this, &PoseListWidget::removeAllContent);
connect(this, &PoseListWidget::removePose, document, &SkeletonDocument::removePose);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QTreeWidget::customContextMenuRequested, this, &PoseListWidget::showContextMenu);
reload();
}
void PoseListWidget::poseRemoved(QUuid poseId)
{
if (m_currentSelectedPoseId == poseId)
m_currentSelectedPoseId = QUuid();
m_selectedPoseIds.erase(poseId);
m_itemMap.erase(poseId);
}
void PoseListWidget::updatePoseSelectState(QUuid poseId, bool selected)
{
auto findItemResult = m_itemMap.find(poseId);
if (findItemResult == m_itemMap.end()) {
qDebug() << "Find pose item failed:" << poseId;
return;
}
PoseWidget *poseWidget = (PoseWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
poseWidget->updateCheckedState(selected);
}
void PoseListWidget::selectPose(QUuid poseId, bool multiple)
{
if (multiple) {
if (!m_currentSelectedPoseId.isNull()) {
m_selectedPoseIds.insert(m_currentSelectedPoseId);
m_currentSelectedPoseId = QUuid();
}
if (m_selectedPoseIds.find(poseId) != m_selectedPoseIds.end()) {
updatePoseSelectState(poseId, false);
m_selectedPoseIds.erase(poseId);
} else {
updatePoseSelectState(poseId, true);
m_selectedPoseIds.insert(poseId);
}
if (m_selectedPoseIds.size() > 1) {
return;
}
if (m_selectedPoseIds.size() == 1)
poseId = *m_selectedPoseIds.begin();
else
poseId = QUuid();
}
if (!m_selectedPoseIds.empty()) {
for (const auto &id: m_selectedPoseIds) {
updatePoseSelectState(id, false);
}
m_selectedPoseIds.clear();
}
if (m_currentSelectedPoseId != poseId) {
if (!m_currentSelectedPoseId.isNull()) {
updatePoseSelectState(m_currentSelectedPoseId, false);
}
m_currentSelectedPoseId = poseId;
if (!m_currentSelectedPoseId.isNull()) {
updatePoseSelectState(m_currentSelectedPoseId, true);
}
}
}
void PoseListWidget::mousePressEvent(QMouseEvent *event)
{
QModelIndex itemIndex = indexAt(event->pos());
QTreeView::mousePressEvent(event);
if (event->button() == Qt::LeftButton) {
bool multiple = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier);
if (itemIndex.isValid()) {
QTreeWidgetItem *item = itemFromIndex(itemIndex);
auto poseId = QUuid(item->data(itemIndex.column(), Qt::UserRole).toString());
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
bool startAdd = false;
bool stopAdd = false;
std::vector<QUuid> waitQueue;
for (const auto &childId: m_document->poseIdList) {
if (m_shiftStartPoseId == childId || poseId == childId) {
if (startAdd) {
stopAdd = true;
} else {
startAdd = true;
}
}
if (startAdd)
waitQueue.push_back(childId);
if (stopAdd)
break;
}
if (stopAdd && !waitQueue.empty()) {
if (!m_selectedPoseIds.empty()) {
for (const auto &id: m_selectedPoseIds) {
updatePoseSelectState(id, false);
}
m_selectedPoseIds.clear();
}
if (!m_currentSelectedPoseId.isNull()) {
m_currentSelectedPoseId = QUuid();
}
for (const auto &waitId: waitQueue) {
selectPose(waitId, true);
}
}
return;
} else {
m_shiftStartPoseId = poseId;
}
selectPose(poseId, multiple);
return;
}
if (!multiple)
selectPose(QUuid());
}
}
bool PoseListWidget::isPoseSelected(QUuid poseId)
{
return (m_currentSelectedPoseId == poseId ||
m_selectedPoseIds.find(poseId) != m_selectedPoseIds.end());
}
void PoseListWidget::showContextMenu(const QPoint &pos)
{
QMenu contextMenu(this);
std::set<QUuid> unorderedPoseIds = m_selectedPoseIds;
if (!m_currentSelectedPoseId.isNull())
unorderedPoseIds.insert(m_currentSelectedPoseId);
std::vector<QUuid> poseIds;
for (const auto &cand: m_document->poseIdList) {
if (unorderedPoseIds.find(cand) != unorderedPoseIds.end())
poseIds.push_back(cand);
}
QAction modifyAction(tr("Modify"), this);
if (poseIds.size() == 1) {
connect(&modifyAction, &QAction::triggered, this, [=]() {
emit modifyPose(*poseIds.begin());
});
contextMenu.addAction(&modifyAction);
}
QAction copyAction(tr("Copy"), this);
if (!poseIds.empty()) {
connect(&copyAction, &QAction::triggered, this, &PoseListWidget::copy);
contextMenu.addAction(&copyAction);
}
QAction pasteAction(tr("Paste"), this);
if (m_document->hasPastablePosesInClipboard()) {
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
contextMenu.addAction(&pasteAction);
}
QAction deleteAction(tr("Delete"), this);
if (!poseIds.empty()) {
connect(&deleteAction, &QAction::triggered, [=]() {
for (const auto &poseId: poseIds)
emit removePose(poseId);
});
contextMenu.addAction(&deleteAction);
}
contextMenu.exec(mapToGlobal(pos));
}
void PoseListWidget::resizeEvent(QResizeEvent *event)
{
QTreeWidget::resizeEvent(event);
if (calculateColumnCount() != columnCount())
reload();
}
int PoseListWidget::calculateColumnCount()
{
if (nullptr == parentWidget())
return 0;
int columns = parentWidget()->width() / Theme::posePreviewImageSize;
if (0 == columns)
columns = 1;
return columns;
}
void PoseListWidget::reload()
{
removeAllContent();
int columns = calculateColumnCount();
if (0 == columns)
return;
int columnWidth = parentWidget()->width() / columns;
//qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns;
setColumnCount(columns);
for (int i = 0; i < columns; i++)
setColumnWidth(i, columnWidth);
decltype(m_document->poseIdList.size()) poseIndex = 0;
while (poseIndex < m_document->poseIdList.size()) {
QTreeWidgetItem *item = new QTreeWidgetItem(this);
item->setFlags((item->flags() | Qt::ItemIsEditable | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable));
for (int col = 0; col < columns && poseIndex < m_document->poseIdList.size(); col++, poseIndex++) {
const auto &poseId = m_document->poseIdList[poseIndex];
item->setSizeHint(col, QSize(columnWidth, PoseWidget::preferredHeight() + 2));
item->setData(col, Qt::UserRole, poseId.toString());
PoseWidget *widget = new PoseWidget(m_document, poseId);
connect(widget, &PoseWidget::modifyPose, this, &PoseListWidget::modifyPose);
widget->previewWidget()->setGraphicsFunctions(this);
setItemWidget(item, col, widget);
widget->reload();
widget->updateCheckedState(isPoseSelected(poseId));
m_itemMap[poseId] = std::make_pair(item, col);
}
invisibleRootItem()->addChild(item);
}
}
void PoseListWidget::removeAllContent()
{
m_itemMap.clear();
clear();
}
bool PoseListWidget::mouseMove(QMouseEvent *event)
{
return false;
}
bool PoseListWidget::wheel(QWheelEvent *event)
{
return false;
}
bool PoseListWidget::mouseRelease(QMouseEvent *event)
{
return false;
}
bool PoseListWidget::mousePress(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
showContextMenu(mapFromGlobal(event->globalPos()));
return false;
}
return false;
}
bool PoseListWidget::mouseDoubleClick(QMouseEvent *event)
{
return false;
}
bool PoseListWidget::keyPress(QKeyEvent *event)
{
return false;
}
void PoseListWidget::copy()
{
if (m_selectedPoseIds.empty() && m_currentSelectedPoseId.isNull())
return;
std::set<QUuid> limitPoseIds = m_selectedPoseIds;
if (!m_currentSelectedPoseId.isNull())
limitPoseIds.insert(m_currentSelectedPoseId);
std::set<QUuid> emptySet;
SkeletonSnapshot snapshot;
m_document->toSnapshot(&snapshot, emptySet, SkeletonDocumentToSnapshotFor::Poses,
limitPoseIds);
QString snapshotXml;
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(snapshotXml);
}

44
src/poselistwidget.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef POSE_LIST_WIDGET_H
#define POSE_LIST_WIDGET_H
#include <QTreeWidget>
#include <map>
#include "skeletondocument.h"
#include "posewidget.h"
#include "skeletongraphicswidget.h"
class PoseListWidget : public QTreeWidget, public SkeletonGraphicsFunctions
{
Q_OBJECT
signals:
void removePose(QUuid poseId);
void modifyPose(QUuid poseId);
public:
PoseListWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
bool isPoseSelected(QUuid poseId);
public slots:
void reload();
void removeAllContent();
void poseRemoved(QUuid poseId);
void showContextMenu(const QPoint &pos);
void selectPose(QUuid poseId, bool multiple=false);
void copy();
protected:
void resizeEvent(QResizeEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
bool mouseMove(QMouseEvent *event);
bool wheel(QWheelEvent *event);
bool mouseRelease(QMouseEvent *event);
bool mousePress(QMouseEvent *event);
bool mouseDoubleClick(QMouseEvent *event);
bool keyPress(QKeyEvent *event);
private:
int calculateColumnCount();
void updatePoseSelectState(QUuid poseId, bool selected);
const SkeletonDocument *m_document = nullptr;
std::map<QUuid, std::pair<QTreeWidgetItem *, int>> m_itemMap;
std::set<QUuid> m_selectedPoseIds;
QUuid m_currentSelectedPoseId;
QUuid m_shiftStartPoseId;
};
#endif

62
src/posemanagewidget.cpp Normal file
View File

@ -0,0 +1,62 @@
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include "posemanagewidget.h"
#include "theme.h"
#include "poseeditwidget.h"
PoseManageWidget::PoseManageWidget(const SkeletonDocument *document, QWidget *parent) :
QWidget(parent),
m_document(document)
{
QPushButton *addPoseButton = new QPushButton(Theme::awesome()->icon(fa::plus), tr("Add Pose..."));
connect(addPoseButton, &QPushButton::clicked, this, &PoseManageWidget::showAddPoseDialog);
QHBoxLayout *toolsLayout = new QHBoxLayout;
toolsLayout->addWidget(addPoseButton);
m_poseListWidget = new PoseListWidget(document);
connect(m_poseListWidget, &PoseListWidget::modifyPose, this, &PoseManageWidget::showPoseDialog);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(toolsLayout);
mainLayout->addWidget(m_poseListWidget);
setLayout(mainLayout);
}
PoseListWidget *PoseManageWidget::poseListWidget()
{
return m_poseListWidget;
}
QSize PoseManageWidget::sizeHint() const
{
return QSize(Theme::sidebarPreferredWidth, 0);
}
void PoseManageWidget::showAddPoseDialog()
{
showPoseDialog(QUuid());
}
void PoseManageWidget::showPoseDialog(QUuid poseId)
{
PoseEditWidget *poseEditWidget = new PoseEditWidget(m_document);
poseEditWidget->setAttribute(Qt::WA_DeleteOnClose);
if (!poseId.isNull()) {
const SkeletonPose *pose = m_document->findPose(poseId);
if (nullptr != pose) {
poseEditWidget->setEditPoseId(poseId);
poseEditWidget->setEditPoseName(pose->name);
poseEditWidget->setEditParameters(pose->parameters);
poseEditWidget->clearUnsaveState();
}
}
poseEditWidget->show();
connect(poseEditWidget, &QDialog::destroyed, [=]() {
emit unregisterDialog((QWidget *)poseEditWidget);
});
emit registerDialog((QWidget *)poseEditWidget);
}

26
src/posemanagewidget.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef POSE_MANAGE_WIDGET_H
#define POSE_MANAGE_WIDGET_H
#include <QWidget>
#include "skeletondocument.h"
#include "poselistwidget.h"
class PoseManageWidget : public QWidget
{
Q_OBJECT
signals:
void registerDialog(QWidget *widget);
void unregisterDialog(QWidget *widget);
public:
PoseManageWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
PoseListWidget *poseListWidget();
protected:
virtual QSize sizeHint() const;
public slots:
void showAddPoseDialog();
void showPoseDialog(QUuid poseId);
private:
const SkeletonDocument *m_document = nullptr;
PoseListWidget *m_poseListWidget = nullptr;
};
#endif

View File

@ -49,12 +49,11 @@ void PosePreviewManager::poseMeshReady()
delete m_previewMesh; delete m_previewMesh;
m_previewMesh = m_poseMeshCreator->takeResultMesh(); m_previewMesh = m_poseMeshCreator->takeResultMesh();
emit resultPreviewMeshChanged();
qDebug() << "Pose mesh generation done"; qDebug() << "Pose mesh generation done";
delete m_poseMeshCreator; delete m_poseMeshCreator;
m_poseMeshCreator = nullptr; m_poseMeshCreator = nullptr;
emit resultPreviewMeshChanged();
emit renderDone(); emit renderDone();
} }

View File

@ -0,0 +1,66 @@
#include <QGuiApplication>
#include <QElapsedTimer>
#include "posepreviewsgenerator.h"
#include "tetrapodposer.h"
#include "posemeshcreator.h"
PosePreviewsGenerator::PosePreviewsGenerator(const std::vector<AutoRiggerBone> *rigBones,
const std::map<int, AutoRiggerVertexWeights> *rigWeights,
const MeshResultContext &meshResultContext) :
m_rigBones(*rigBones),
m_rigWeights(*rigWeights),
m_meshResultContext(new MeshResultContext(meshResultContext))
{
}
PosePreviewsGenerator::~PosePreviewsGenerator()
{
for (auto &item: m_previews) {
delete item.second;
}
delete m_meshResultContext;
}
void PosePreviewsGenerator::addPose(QUuid poseId, const std::map<QString, std::map<QString, QString>> &pose)
{
m_poses.push_back(std::make_pair(poseId, pose));
}
const std::set<QUuid> &PosePreviewsGenerator::generatedPreviewPoseIds()
{
return m_generatedPoseIds;
}
MeshLoader *PosePreviewsGenerator::takePreview(QUuid poseId)
{
MeshLoader *resultMesh = m_previews[poseId];
m_previews[poseId] = nullptr;
return resultMesh;
}
void PosePreviewsGenerator::process()
{
QElapsedTimer countTimeConsumed;
countTimeConsumed.start();
TetrapodPoser *poser = new TetrapodPoser(m_rigBones);
for (const auto &pose: m_poses) {
poser->parameters() = pose.second;
poser->commit();
PoseMeshCreator *poseMeshCreator = new PoseMeshCreator(*poser, *m_meshResultContext, m_rigWeights);
poseMeshCreator->createMesh();
m_previews[pose.first] = poseMeshCreator->takeResultMesh();
delete poseMeshCreator;
poser->reset();
m_generatedPoseIds.insert(pose.first);
}
delete poser;
qDebug() << "The pose previews generation took" << countTimeConsumed.elapsed() << "milliseconds";
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

View File

@ -0,0 +1,35 @@
#ifndef POSE_PREVIEWS_GENERATOR_H
#define POSE_PREVIEWS_GENERATOR_H
#include <QObject>
#include <map>
#include <QUuid>
#include <vector>
#include "meshloader.h"
#include "autorigger.h"
#include "meshresultcontext.h"
class PosePreviewsGenerator : public QObject
{
Q_OBJECT
public:
PosePreviewsGenerator(const std::vector<AutoRiggerBone> *rigBones,
const std::map<int, AutoRiggerVertexWeights> *rigWeights,
const MeshResultContext &meshResultContext);
~PosePreviewsGenerator();
void addPose(QUuid poseId, const std::map<QString, std::map<QString, QString>> &pose);
const std::set<QUuid> &generatedPreviewPoseIds();
MeshLoader *takePreview(QUuid poseId);
signals:
void finished();
public slots:
void process();
private:
std::vector<AutoRiggerBone> m_rigBones;
std::map<int, AutoRiggerVertexWeights> m_rigWeights;
MeshResultContext *m_meshResultContext = nullptr;
std::vector<std::pair<QUuid, std::map<QString, std::map<QString, QString>>>> m_poses;
std::map<QUuid, MeshLoader *> m_previews;
std::set<QUuid> m_generatedPoseIds;
};
#endif

View File

@ -16,7 +16,7 @@ public:
const std::vector<AutoRiggerBone> &bones() const; const std::vector<AutoRiggerBone> &bones() const;
const std::vector<JointNode> &resultNodes() const; const std::vector<JointNode> &resultNodes() const;
std::map<QString, std::map<QString, QString>> &parameters(); std::map<QString, std::map<QString, QString>> &parameters();
void commit(); virtual void commit();
void reset(); void reset();
protected: protected:
std::vector<AutoRiggerBone> m_bones; std::vector<AutoRiggerBone> m_bones;

93
src/posewidget.cpp Normal file
View File

@ -0,0 +1,93 @@
#include <QVBoxLayout>
#include "posewidget.h"
PoseWidget::PoseWidget(const SkeletonDocument *document, QUuid poseId) :
m_poseId(poseId),
m_document(document)
{
setObjectName("PoseFrame");
m_previewWidget = new ModelWidget(this);
m_previewWidget->setFixedSize(Theme::posePreviewImageSize, Theme::posePreviewImageSize);
m_previewWidget->enableMove(false);
m_previewWidget->enableZoom(false);
m_nameLabel = new QLabel;
m_nameLabel->setAlignment(Qt::AlignCenter);
m_nameLabel->setStyleSheet("background: qlineargradient(x1:0.5 y1:-15.5, x2:0.5 y2:1, stop:0 " + Theme::white.name() + ", stop:1 #252525);");
QFont nameFont;
nameFont.setWeight(QFont::Light);
nameFont.setPixelSize(9);
nameFont.setBold(false);
m_nameLabel->setFont(nameFont);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addStretch();
mainLayout->addWidget(m_nameLabel);
setLayout(mainLayout);
setFixedSize(Theme::posePreviewImageSize, PoseWidget::preferredHeight());
connect(document, &SkeletonDocument::poseNameChanged, this, &PoseWidget::updateName);
connect(document, &SkeletonDocument::posePreviewChanged, this, &PoseWidget::updatePreview);
}
void PoseWidget::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
m_previewWidget->move((width() - Theme::posePreviewImageSize) / 2, 0);
}
int PoseWidget::preferredHeight()
{
return Theme::posePreviewImageSize;
}
void PoseWidget::reload()
{
updatePreview();
updateName();
}
void PoseWidget::updatePreview()
{
const SkeletonPose *pose = m_document->findPose(m_poseId);
if (!pose) {
qDebug() << "Pose not found:" << m_poseId;
return;
}
MeshLoader *previewMesh = pose->takePreviewMesh();
m_previewWidget->updateMesh(previewMesh);
}
void PoseWidget::updateName()
{
const SkeletonPose *pose = m_document->findPose(m_poseId);
if (!pose) {
qDebug() << "Pose not found:" << m_poseId;
return;
}
m_nameLabel->setText(pose->name);
}
void PoseWidget::updateCheckedState(bool checked)
{
if (checked)
setStyleSheet("#PoseFrame {border: 1px solid " + Theme::red.name() + ";}");
else
setStyleSheet("#PoseFrame {border: 1px solid transparent;}");
}
ModelWidget *PoseWidget::previewWidget()
{
return m_previewWidget;
}
void PoseWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QFrame::mouseDoubleClickEvent(event);
emit modifyPose(m_poseId);
}

32
src/posewidget.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef POSE_WIDGET_H
#define POSE_WIDGET_H
#include <QFrame>
#include <QLabel>
#include "skeletondocument.h"
#include "modelwidget.h"
class PoseWidget : public QFrame
{
Q_OBJECT
signals:
void modifyPose(QUuid poseId);
public:
PoseWidget(const SkeletonDocument *document, QUuid poseId);
static int preferredHeight();
ModelWidget *previewWidget();
protected:
void mouseDoubleClickEvent(QMouseEvent *event) override;
public slots:
void reload();
void updatePreview();
void updateName();
void updateCheckedState(bool checked);
void resizeEvent(QResizeEvent *event) override;
private:
QUuid m_poseId;
const SkeletonDocument *m_document = nullptr;
ModelWidget *m_previewWidget = nullptr;
QLabel *m_nameLabel = nullptr;
};
#endif

View File

@ -51,7 +51,8 @@ SkeletonDocument::SkeletonDocument() :
m_resultRigBones(nullptr), m_resultRigBones(nullptr),
m_resultRigWeights(nullptr), m_resultRigWeights(nullptr),
m_isRigObsolete(false), m_isRigObsolete(false),
m_riggedResultContext(new MeshResultContext) m_riggedResultContext(new MeshResultContext),
m_posePreviewsGenerator(nullptr)
{ {
} }
@ -281,8 +282,6 @@ QUuid SkeletonDocument::createNode(float x, float y, float z, float radius, QUui
nodeMap[node.id] = node; nodeMap[node.id] = node;
partMap[partId].nodeIds.push_back(node.id); partMap[partId].nodeIds.push_back(node.id);
qDebug() << "Add node " << node.id << x << y << z;
emit nodeAdded(node.id); emit nodeAdded(node.id);
if (nullptr != fromNode) { if (nullptr != fromNode) {
@ -306,6 +305,66 @@ QUuid SkeletonDocument::createNode(float x, float y, float z, float radius, QUui
return node.id; return node.id;
} }
void SkeletonDocument::addPose(QString name, std::map<QString, std::map<QString, QString>> parameters)
{
QUuid newPoseId = QUuid::createUuid();
auto &pose = poseMap[newPoseId];
pose.id = newPoseId;
pose.name = name;
pose.parameters = parameters;
pose.dirty = true;
poseIdList.push_back(newPoseId);
emit poseAdded(newPoseId);
emit poseListChanged();
emit optionsChanged();
}
void SkeletonDocument::removePose(QUuid poseId)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Remove a none exist pose:" << poseId;
return;
}
poseIdList.erase(std::remove(poseIdList.begin(), poseIdList.end(), poseId), poseIdList.end());
poseMap.erase(findPoseResult);
emit poseListChanged();
emit poseRemoved(poseId);
emit optionsChanged();
}
void SkeletonDocument::setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId;
return;
}
findPoseResult->second.parameters = parameters;
findPoseResult->second.dirty = true;
emit poseParametersChanged(poseId);
emit optionsChanged();
}
void SkeletonDocument::renamePose(QUuid poseId, QString name)
{
auto findPoseResult = poseMap.find(poseId);
if (findPoseResult == poseMap.end()) {
qDebug() << "Find pose failed:" << poseId;
return;
}
if (findPoseResult->second.name == name)
return;
findPoseResult->second.name = name;
emit poseNameChanged(poseId);
emit optionsChanged();
}
const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
{ {
const SkeletonNode *firstNode = nullptr; const SkeletonNode *firstNode = nullptr;
@ -446,6 +505,14 @@ const SkeletonComponent *SkeletonDocument::findComponent(QUuid componentId) cons
return &it->second; return &it->second;
} }
const SkeletonPose *SkeletonDocument::findPose(QUuid poseId) const
{
auto it = poseMap.find(poseId);
if (it == poseMap.end())
return nullptr;
return &it->second;
}
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount) void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
{ {
auto it = nodeMap.find(nodeId); auto it = nodeMap.find(nodeId);
@ -675,128 +742,151 @@ void SkeletonDocument::markAllDirty()
} }
} }
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds) const void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds,
SkeletonDocumentToSnapshotFor forWhat, const std::set<QUuid> &limitPoseIds) const
{ {
std::set<QUuid> limitPartIds; if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
std::set<QUuid> limitComponentIds; SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
for (const auto &nodeId: limitNodeIds) { std::set<QUuid> limitPartIds;
const SkeletonNode *node = findNode(nodeId); std::set<QUuid> limitComponentIds;
if (!node) for (const auto &nodeId: limitNodeIds) {
continue; const SkeletonNode *node = findNode(nodeId);
const SkeletonPart *part = findPart(node->partId); if (!node)
if (!part) continue;
continue; const SkeletonPart *part = findPart(node->partId);
limitPartIds.insert(node->partId); if (!part)
const SkeletonComponent *component = findComponent(part->componentId); continue;
while (component) { limitPartIds.insert(node->partId);
limitComponentIds.insert(component->id); const SkeletonComponent *component = findComponent(part->componentId);
if (component->id.isNull()) while (component) {
break; limitComponentIds.insert(component->id);
component = findComponent(component->parentId); if (component->id.isNull())
break;
component = findComponent(component->parentId);
}
}
for (const auto &partIt : partMap) {
if (!limitPartIds.empty() && limitPartIds.find(partIt.first) == limitPartIds.end())
continue;
std::map<QString, QString> part;
part["id"] = partIt.second.id.toString();
part["visible"] = partIt.second.visible ? "true" : "false";
part["locked"] = partIt.second.locked ? "true" : "false";
part["subdived"] = partIt.second.subdived ? "true" : "false";
part["disabled"] = partIt.second.disabled ? "true" : "false";
part["xMirrored"] = partIt.second.xMirrored ? "true" : "false";
part["zMirrored"] = partIt.second.zMirrored ? "true" : "false";
part["rounded"] = partIt.second.rounded ? "true" : "false";
part["wrapped"] = partIt.second.wrapped ? "true" : "false";
part["dirty"] = partIt.second.dirty ? "true" : "false";
if (partIt.second.hasColor)
part["color"] = partIt.second.color.name();
if (partIt.second.deformThicknessAdjusted())
part["deformThickness"] = QString::number(partIt.second.deformThickness);
if (partIt.second.deformWidthAdjusted())
part["deformWidth"] = QString::number(partIt.second.deformWidth);
if (!partIt.second.name.isEmpty())
part["name"] = partIt.second.name;
snapshot->parts[part["id"]] = part;
}
for (const auto &nodeIt: nodeMap) {
if (!limitNodeIds.empty() && limitNodeIds.find(nodeIt.first) == limitNodeIds.end())
continue;
std::map<QString, QString> node;
node["id"] = nodeIt.second.id.toString();
node["radius"] = QString::number(nodeIt.second.radius);
node["x"] = QString::number(nodeIt.second.x);
node["y"] = QString::number(nodeIt.second.y);
node["z"] = QString::number(nodeIt.second.z);
node["partId"] = nodeIt.second.partId.toString();
if (nodeIt.second.boneMark != SkeletonBoneMark::None)
node["boneMark"] = SkeletonBoneMarkToString(nodeIt.second.boneMark);
if (!nodeIt.second.name.isEmpty())
node["name"] = nodeIt.second.name;
snapshot->nodes[node["id"]] = node;
}
for (const auto &edgeIt: edgeMap) {
if (edgeIt.second.nodeIds.size() != 2)
continue;
if (!limitNodeIds.empty() &&
(limitNodeIds.find(edgeIt.second.nodeIds[0]) == limitNodeIds.end() ||
limitNodeIds.find(edgeIt.second.nodeIds[1]) == limitNodeIds.end()))
continue;
std::map<QString, QString> edge;
edge["id"] = edgeIt.second.id.toString();
edge["from"] = edgeIt.second.nodeIds[0].toString();
edge["to"] = edgeIt.second.nodeIds[1].toString();
edge["partId"] = edgeIt.second.partId.toString();
if (!edgeIt.second.name.isEmpty())
edge["name"] = edgeIt.second.name;
snapshot->edges[edge["id"]] = edge;
}
for (const auto &componentIt: componentMap) {
if (!limitComponentIds.empty() && limitComponentIds.find(componentIt.first) == limitComponentIds.end())
continue;
std::map<QString, QString> component;
component["id"] = componentIt.second.id.toString();
if (!componentIt.second.name.isEmpty())
component["name"] = componentIt.second.name;
component["expanded"] = componentIt.second.expanded ? "true" : "false";
component["inverse"] = componentIt.second.inverse ? "true" : "false";
component["dirty"] = componentIt.second.dirty ? "true" : "false";
if (componentIt.second.smoothAllAdjusted())
component["smoothAll"] = QString::number(componentIt.second.smoothAll);
if (componentIt.second.smoothSeamAdjusted())
component["smoothSeam"] = QString::number(componentIt.second.smoothSeam);
QStringList childIdList;
for (const auto &childId: componentIt.second.childrenIds) {
childIdList.append(childId.toString());
}
QString children = childIdList.join(",");
if (!children.isEmpty())
component["children"] = children;
QString linkData = componentIt.second.linkData();
if (!linkData.isEmpty()) {
component["linkData"] = linkData;
component["linkDataType"] = componentIt.second.linkDataType();
}
if (!componentIt.second.name.isEmpty())
component["name"] = componentIt.second.name;
snapshot->components[component["id"]] = component;
}
if (limitComponentIds.empty() || limitComponentIds.find(QUuid()) != limitComponentIds.end()) {
QStringList childIdList;
for (const auto &childId: rootComponent.childrenIds) {
childIdList.append(childId.toString());
}
QString children = childIdList.join(",");
if (!children.isEmpty())
snapshot->rootComponent["children"] = children;
} }
} }
for (const auto &partIt : partMap) { if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
if (!limitPartIds.empty() && limitPartIds.find(partIt.first) == limitPartIds.end()) SkeletonDocumentToSnapshotFor::Poses == forWhat) {
continue; for (const auto &poseId: poseIdList) {
std::map<QString, QString> part; if (!limitPoseIds.empty() && limitPoseIds.find(poseId) == limitPoseIds.end())
part["id"] = partIt.second.id.toString(); continue;
part["visible"] = partIt.second.visible ? "true" : "false"; auto findPoseResult = poseMap.find(poseId);
part["locked"] = partIt.second.locked ? "true" : "false"; if (findPoseResult == poseMap.end()) {
part["subdived"] = partIt.second.subdived ? "true" : "false"; qDebug() << "Find pose failed:" << poseId;
part["disabled"] = partIt.second.disabled ? "true" : "false"; continue;
part["xMirrored"] = partIt.second.xMirrored ? "true" : "false"; }
part["zMirrored"] = partIt.second.zMirrored ? "true" : "false"; auto &poseIt = *findPoseResult;
part["rounded"] = partIt.second.rounded ? "true" : "false"; std::map<QString, QString> pose;
part["wrapped"] = partIt.second.wrapped ? "true" : "false"; pose["id"] = poseIt.second.id.toString();
part["dirty"] = partIt.second.dirty ? "true" : "false"; if (!poseIt.second.name.isEmpty())
if (partIt.second.hasColor) pose["name"] = poseIt.second.name;
part["color"] = partIt.second.color.name(); snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters));
if (partIt.second.deformThicknessAdjusted())
part["deformThickness"] = QString::number(partIt.second.deformThickness);
if (partIt.second.deformWidthAdjusted())
part["deformWidth"] = QString::number(partIt.second.deformWidth);
if (!partIt.second.name.isEmpty())
part["name"] = partIt.second.name;
snapshot->parts[part["id"]] = part;
}
for (const auto &nodeIt: nodeMap) {
if (!limitNodeIds.empty() && limitNodeIds.find(nodeIt.first) == limitNodeIds.end())
continue;
std::map<QString, QString> node;
node["id"] = nodeIt.second.id.toString();
node["radius"] = QString::number(nodeIt.second.radius);
node["x"] = QString::number(nodeIt.second.x);
node["y"] = QString::number(nodeIt.second.y);
node["z"] = QString::number(nodeIt.second.z);
node["partId"] = nodeIt.second.partId.toString();
if (nodeIt.second.boneMark != SkeletonBoneMark::None)
node["boneMark"] = SkeletonBoneMarkToString(nodeIt.second.boneMark);
if (!nodeIt.second.name.isEmpty())
node["name"] = nodeIt.second.name;
snapshot->nodes[node["id"]] = node;
}
for (const auto &edgeIt: edgeMap) {
if (edgeIt.second.nodeIds.size() != 2)
continue;
if (!limitNodeIds.empty() &&
(limitNodeIds.find(edgeIt.second.nodeIds[0]) == limitNodeIds.end() ||
limitNodeIds.find(edgeIt.second.nodeIds[1]) == limitNodeIds.end()))
continue;
std::map<QString, QString> edge;
edge["id"] = edgeIt.second.id.toString();
edge["from"] = edgeIt.second.nodeIds[0].toString();
edge["to"] = edgeIt.second.nodeIds[1].toString();
edge["partId"] = edgeIt.second.partId.toString();
if (!edgeIt.second.name.isEmpty())
edge["name"] = edgeIt.second.name;
snapshot->edges[edge["id"]] = edge;
}
for (const auto &componentIt: componentMap) {
if (!limitComponentIds.empty() && limitComponentIds.find(componentIt.first) == limitComponentIds.end())
continue;
std::map<QString, QString> component;
component["id"] = componentIt.second.id.toString();
if (!componentIt.second.name.isEmpty())
component["name"] = componentIt.second.name;
component["expanded"] = componentIt.second.expanded ? "true" : "false";
component["inverse"] = componentIt.second.inverse ? "true" : "false";
component["dirty"] = componentIt.second.dirty ? "true" : "false";
if (componentIt.second.smoothAllAdjusted())
component["smoothAll"] = QString::number(componentIt.second.smoothAll);
if (componentIt.second.smoothSeamAdjusted())
component["smoothSeam"] = QString::number(componentIt.second.smoothSeam);
QStringList childIdList;
for (const auto &childId: componentIt.second.childrenIds) {
childIdList.append(childId.toString());
} }
QString children = childIdList.join(",");
if (!children.isEmpty())
component["children"] = children;
QString linkData = componentIt.second.linkData();
if (!linkData.isEmpty()) {
component["linkData"] = linkData;
component["linkDataType"] = componentIt.second.linkDataType();
}
if (!componentIt.second.name.isEmpty())
component["name"] = componentIt.second.name;
snapshot->components[component["id"]] = component;
} }
if (limitComponentIds.empty() || limitComponentIds.find(QUuid()) != limitComponentIds.end()) { if (SkeletonDocumentToSnapshotFor::Document == forWhat) {
QStringList childIdList; std::map<QString, QString> canvas;
for (const auto &childId: rootComponent.childrenIds) { canvas["originX"] = QString::number(originX);
childIdList.append(childId.toString()); canvas["originY"] = QString::number(originY);
} canvas["originZ"] = QString::number(originZ);
QString children = childIdList.join(","); canvas["rigType"] = RigTypeToString(rigType);
if (!children.isEmpty()) snapshot->canvas = canvas;
snapshot->rootComponent["children"] = children;
} }
std::map<QString, QString> canvas;
canvas["originX"] = QString::number(originX);
canvas["originY"] = QString::number(originY);
canvas["originZ"] = QString::number(originZ);
canvas["rigType"] = RigTypeToString(rigType);
snapshot->canvas = canvas;
} }
void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste) void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste)
@ -921,11 +1011,11 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
const auto &smoothSeamIt = componentKv.second.find("smoothSeam"); const auto &smoothSeamIt = componentKv.second.find("smoothSeam");
if (smoothSeamIt != componentKv.second.end()) if (smoothSeamIt != componentKv.second.end())
component.setSmoothSeam(smoothSeamIt->second.toFloat()); component.setSmoothSeam(smoothSeamIt->second.toFloat());
qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name; //qDebug() << "Add component:" << component.id << " old:" << componentKv.first << "name:" << component.name;
if ("partId" == linkDataType) { if ("partId" == linkDataType) {
QUuid partId = oldNewIdMap[QUuid(linkData)]; QUuid partId = oldNewIdMap[QUuid(linkData)];
component.linkToPartId = partId; component.linkToPartId = partId;
qDebug() << "Add part:" << partId << " from component:" << component.id; //qDebug() << "Add part:" << partId << " from component:" << component.id;
partMap[partId].componentId = component.id; partMap[partId].componentId = component.id;
if (inversePartIds.find(partId) != inversePartIds.end()) if (inversePartIds.find(partId) != inversePartIds.end())
component.inverse = true; component.inverse = true;
@ -941,7 +1031,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
QUuid componentId = oldNewIdMap[QUuid(childId)]; QUuid componentId = oldNewIdMap[QUuid(childId)];
if (componentMap.find(componentId) == componentMap.end()) if (componentMap.find(componentId) == componentMap.end())
continue; continue;
qDebug() << "Add root component:" << componentId; //qDebug() << "Add root component:" << componentId;
rootComponent.addChild(componentId); rootComponent.addChild(componentId);
} }
} }
@ -955,11 +1045,22 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
QUuid childComponentId = oldNewIdMap[QUuid(childId)]; QUuid childComponentId = oldNewIdMap[QUuid(childId)];
if (componentMap.find(childComponentId) == componentMap.end()) if (componentMap.find(childComponentId) == componentMap.end())
continue; continue;
qDebug() << "Add child component:" << childComponentId << "to" << componentId; //qDebug() << "Add child component:" << childComponentId << "to" << componentId;
componentMap[componentId].addChild(childComponentId); componentMap[componentId].addChild(childComponentId);
componentMap[childComponentId].parentId = componentId; componentMap[childComponentId].parentId = componentId;
} }
} }
for (const auto &poseIt: snapshot.poses) {
QUuid newPoseId = QUuid::createUuid();
auto &newPose = poseMap[newPoseId];
newPose.id = newPoseId;
const auto &poseAttributes = poseIt.first;
newPose.name = valueOfKeyInMapOrEmpty(poseAttributes, "name");
newPose.parameters = poseIt.second;
oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(poseAttributes, "id"))] = newPoseId;
poseIdList.push_back(newPoseId);
emit poseAdded(newPoseId);
}
for (const auto &nodeIt: newAddedNodeIds) { for (const auto &nodeIt: newAddedNodeIds) {
emit nodeAdded(nodeIt); emit nodeAdded(nodeIt);
@ -989,6 +1090,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
for (const auto &edgeIt: newAddedEdgeIds) { for (const auto &edgeIt: newAddedEdgeIds) {
emit checkEdge(edgeIt); emit checkEdge(edgeIt);
} }
if (!snapshot.poses.empty())
emit poseListChanged();
} }
void SkeletonDocument::reset() void SkeletonDocument::reset()
@ -1001,6 +1105,8 @@ void SkeletonDocument::reset()
edgeMap.clear(); edgeMap.clear();
partMap.clear(); partMap.clear();
componentMap.clear(); componentMap.clear();
poseMap.clear();
poseIdList.clear();
rootComponent = SkeletonComponent(); rootComponent = SkeletonComponent();
emit cleanup(); emit cleanup();
emit skeletonChanged(); emit skeletonChanged();
@ -1954,12 +2060,23 @@ void SkeletonDocument::paste()
} }
} }
bool SkeletonDocument::hasPastableContentInClipboard() const bool SkeletonDocument::hasPastableNodesInClipboard() const
{ {
const QClipboard *clipboard = QApplication::clipboard(); const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData(); const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) { if (mimeData->hasText()) {
if (-1 != mimeData->text().left(1000).indexOf("partIdList")) if (-1 != mimeData->text().left(1000).indexOf("<node "))
return true;
}
return false;
}
bool SkeletonDocument::hasPastablePosesInClipboard() const
{
const QClipboard *clipboard = QApplication::clipboard();
const QMimeData *mimeData = clipboard->mimeData();
if (mimeData->hasText()) {
if (-1 != mimeData->text().right(1000).indexOf("<pose "))
return true; return true;
} }
return false; return false;
@ -2337,3 +2454,63 @@ const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const
{ {
return *m_riggedResultContext; return *m_riggedResultContext;
} }
void SkeletonDocument::generatePosePreviews()
{
if (nullptr != m_posePreviewsGenerator) {
return;
}
const std::vector<AutoRiggerBone> *rigBones = resultRigBones();
const std::map<int, AutoRiggerVertexWeights> *rigWeights = resultRigWeights();
if (nullptr == rigBones || nullptr == rigWeights) {
return;
}
QThread *thread = new QThread;
m_posePreviewsGenerator = new PosePreviewsGenerator(rigBones,
rigWeights, *m_riggedResultContext);
bool hasDirtyPose = false;
for (auto &poseIt: poseMap) {
if (!poseIt.second.dirty)
continue;
m_posePreviewsGenerator->addPose(poseIt.first, poseIt.second.parameters);
poseIt.second.dirty = false;
hasDirtyPose = true;
}
if (!hasDirtyPose) {
delete m_posePreviewsGenerator;
m_posePreviewsGenerator = nullptr;
delete thread;
return;
}
qDebug() << "Pose previews generating..";
m_posePreviewsGenerator->moveToThread(thread);
connect(thread, &QThread::started, m_posePreviewsGenerator, &PosePreviewsGenerator::process);
connect(m_posePreviewsGenerator, &PosePreviewsGenerator::finished, this, &SkeletonDocument::posePreviewsReady);
connect(m_posePreviewsGenerator, &PosePreviewsGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}
void SkeletonDocument::posePreviewsReady()
{
for (const auto &poseId: m_posePreviewsGenerator->generatedPreviewPoseIds()) {
auto pose = poseMap.find(poseId);
if (pose != poseMap.end()) {
MeshLoader *resultPartPreviewMesh = m_posePreviewsGenerator->takePreview(poseId);
pose->second.updatePreviewMesh(resultPartPreviewMesh);
emit posePreviewChanged(poseId);
}
}
delete m_posePreviewsGenerator;
m_posePreviewsGenerator = nullptr;
qDebug() << "Pose previews generation done";
generatePosePreviews();
}

View File

@ -20,6 +20,7 @@
#include "skeletonbonemark.h" #include "skeletonbonemark.h"
#include "riggenerator.h" #include "riggenerator.h"
#include "rigtype.h" #include "rigtype.h"
#include "posepreviewsgenerator.h"
class SkeletonNode class SkeletonNode
{ {
@ -345,6 +346,43 @@ private:
std::set<QUuid> m_childrenIdSet; std::set<QUuid> m_childrenIdSet;
}; };
class SkeletonPose
{
public:
SkeletonPose()
{
}
~SkeletonPose()
{
delete m_previewMesh;
}
QUuid id;
QString name;
bool dirty = true;
std::map<QString, std::map<QString, QString>> parameters;
void updatePreviewMesh(MeshLoader *previewMesh)
{
delete m_previewMesh;
m_previewMesh = previewMesh;
}
MeshLoader *takePreviewMesh() const
{
if (nullptr == m_previewMesh)
return nullptr;
return new MeshLoader(*m_previewMesh);
}
private:
Q_DISABLE_COPY(SkeletonPose);
MeshLoader *m_previewMesh = nullptr;
};
enum class SkeletonDocumentToSnapshotFor
{
Document = 0,
Nodes,
Poses
};
class SkeletonDocument : public QObject class SkeletonDocument : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -406,6 +444,12 @@ signals:
void checkEdge(QUuid edgeId); void checkEdge(QUuid edgeId);
void optionsChanged(); void optionsChanged();
void rigTypeChanged(); void rigTypeChanged();
void poseAdded(QUuid poseId);
void poseRemoved(QUuid);
void poseListChanged();
void poseNameChanged(QUuid poseId);
void poseParametersChanged(QUuid poseId);
void posePreviewChanged(QUuid poseId);
public: // need initialize public: // need initialize
float originX; float originX;
float originY; float originY;
@ -428,10 +472,14 @@ public:
std::map<QUuid, SkeletonNode> nodeMap; std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap; std::map<QUuid, SkeletonEdge> edgeMap;
std::map<QUuid, SkeletonComponent> componentMap; std::map<QUuid, SkeletonComponent> componentMap;
std::map<QUuid, SkeletonPose> poseMap;
std::vector<QUuid> poseIdList;
SkeletonComponent rootComponent; SkeletonComponent rootComponent;
QImage turnaround; QImage turnaround;
QImage preview; QImage preview;
void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>()) const; void toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUuid> &limitNodeIds=std::set<QUuid>(),
SkeletonDocumentToSnapshotFor forWhat=SkeletonDocumentToSnapshotFor::Document,
const std::set<QUuid> &limitPoseIds=std::set<QUuid>()) const;
void fromSnapshot(const SkeletonSnapshot &snapshot); void fromSnapshot(const SkeletonSnapshot &snapshot);
void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true); void addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
const SkeletonNode *findNode(QUuid nodeId) const; const SkeletonNode *findNode(QUuid nodeId) const;
@ -441,6 +489,7 @@ public:
const SkeletonComponent *findComponent(QUuid componentId) const; const SkeletonComponent *findComponent(QUuid componentId) const;
const SkeletonComponent *findComponentParent(QUuid componentId) const; const SkeletonComponent *findComponentParent(QUuid componentId) const;
QUuid findComponentParentId(QUuid componentId) const; QUuid findComponentParentId(QUuid componentId) const;
const SkeletonPose *findPose(QUuid poseId) const;
MeshLoader *takeResultMesh(); MeshLoader *takeResultMesh();
MeshLoader *takeResultTextureMesh(); MeshLoader *takeResultTextureMesh();
MeshLoader *takeResultRigWeightMesh(); MeshLoader *takeResultRigWeightMesh();
@ -448,7 +497,8 @@ public:
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const; const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const;
void updateTurnaround(const QImage &image); void updateTurnaround(const QImage &image);
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget); void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
bool hasPastableContentInClipboard() const; bool hasPastableNodesInClipboard() const;
bool hasPastablePosesInClipboard() const;
bool undoable() const; bool undoable() const;
bool redoable() const; bool redoable() const;
bool isNodeEditable(QUuid nodeId) const; bool isNodeEditable(QUuid nodeId) const;
@ -489,6 +539,8 @@ public slots:
void ambientOcclusionTextureReady(); void ambientOcclusionTextureReady();
void generateRig(); void generateRig();
void rigReady(); void rigReady();
void generatePosePreviews();
void posePreviewsReady();
void setPartLockState(QUuid partId, bool locked); void setPartLockState(QUuid partId, bool locked);
void setPartVisibleState(QUuid partId, bool visible); void setPartVisibleState(QUuid partId, bool visible);
void setPartSubdivState(QUuid partId, bool subdived); void setPartSubdivState(QUuid partId, bool subdived);
@ -543,6 +595,10 @@ public slots:
void disableAllPositionRelatedLocks(); void disableAllPositionRelatedLocks();
void toggleSmoothNormal(); void toggleSmoothNormal();
void setRigType(RigType toRigType); void setRigType(RigType toRigType);
void addPose(QString name, std::map<QString, std::map<QString, QString>> parameters);
void removePose(QUuid poseId);
void setPoseParameters(QUuid poseId, std::map<QString, std::map<QString, QString>> parameters);
void renamePose(QUuid poseId, QString name);
private: private:
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId); void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid()); void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
@ -583,6 +639,7 @@ private: // need initialize
std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights; std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights;
bool m_isRigObsolete; bool m_isRigObsolete;
MeshResultContext *m_riggedResultContext; MeshResultContext *m_riggedResultContext;
PosePreviewsGenerator *m_posePreviewsGenerator;
private: private:
static unsigned long m_maxSnapshot; static unsigned long m_maxSnapshot;
std::deque<SkeletonHistoryItem> m_undoItems; std::deque<SkeletonHistoryItem> m_undoItems;

View File

@ -30,7 +30,6 @@
#include "skeletonparttreewidget.h" #include "skeletonparttreewidget.h"
#include "rigwidget.h" #include "rigwidget.h"
#include "markiconcreator.h" #include "markiconcreator.h"
#include "tetrapodposeeditwidget.h"
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16; int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
@ -55,7 +54,7 @@ void SkeletonDocumentWindow::showAcknowlegements()
{ {
if (!g_acknowlegementsWidget) { if (!g_acknowlegementsWidget) {
g_acknowlegementsWidget = new QTextBrowser; g_acknowlegementsWidget = new QTextBrowser;
g_acknowlegementsWidget->setWindowTitle(APP_NAME); g_acknowlegementsWidget->setWindowTitle(unifiedWindowTitle(tr("Acknowlegements")));
g_acknowlegementsWidget->setMinimumSize(QSize(400, 300)); g_acknowlegementsWidget->setMinimumSize(QSize(400, 300));
QFile file(":/ACKNOWLEDGEMENTS.html"); QFile file(":/ACKNOWLEDGEMENTS.html");
file.open(QFile::ReadOnly | QFile::Text); file.open(QFile::ReadOnly | QFile::Text);
@ -71,7 +70,7 @@ void SkeletonDocumentWindow::showContributors()
{ {
if (!g_contributorsWidget) { if (!g_contributorsWidget) {
g_contributorsWidget = new QTextBrowser; g_contributorsWidget = new QTextBrowser;
g_contributorsWidget->setWindowTitle(APP_NAME); g_contributorsWidget->setWindowTitle(unifiedWindowTitle(tr("Contributors")));
g_contributorsWidget->setMinimumSize(QSize(400, 300)); g_contributorsWidget->setMinimumSize(QSize(400, 300));
QFile authors(":/AUTHORS"); QFile authors(":/AUTHORS");
authors.open(QFile::ReadOnly | QFile::Text); authors.open(QFile::ReadOnly | QFile::Text);
@ -225,16 +224,21 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
updateRigWeightRenderWidget(); updateRigWeightRenderWidget();
}); });
//QDockWidget *animationDocker = new QDockWidget(tr("Animation"), this); QDockWidget *poseDocker = new QDockWidget(tr("Poses"), this);
//animationDocker->setAllowedAreas(Qt::RightDockWidgetArea); poseDocker->setAllowedAreas(Qt::RightDockWidgetArea);
//TetrapodPoseEditWidget *tetrapodPoseEditWidget = new TetrapodPoseEditWidget(m_document, animationDocker); PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker);
//animationDocker->setWidget(tetrapodPoseEditWidget); poseDocker->setWidget(poseManageWidget);
//AnimationListWidget *animationListWidget = new AnimationListWidget(m_document, animationDocker); connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
//animationDocker->setWidget(animationListWidget); connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
//addDockWidget(Qt::RightDockWidgetArea, animationDocker); addDockWidget(Qt::RightDockWidgetArea, poseDocker);
connect(poseDocker, &QDockWidget::topLevelChanged, [=](bool topLevel) {
Q_UNUSED(topLevel);
for (const auto &pose: m_document->poseMap)
emit m_document->posePreviewChanged(pose.first);
});
tabifyDockWidget(partTreeDocker, rigDocker); tabifyDockWidget(partTreeDocker, rigDocker);
//tabifyDockWidget(rigDocker, animationDocker); tabifyDockWidget(rigDocker, poseDocker);
partTreeDocker->raise(); partTreeDocker->raise();
@ -447,7 +451,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection()); m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection());
m_cutAction->setEnabled(m_graphicsWidget->hasSelection()); m_cutAction->setEnabled(m_graphicsWidget->hasSelection());
m_copyAction->setEnabled(m_graphicsWidget->hasSelection()); m_copyAction->setEnabled(m_graphicsWidget->hasSelection());
m_pasteAction->setEnabled(m_document->hasPastableContentInClipboard()); m_pasteAction->setEnabled(m_document->hasPastableNodesInClipboard());
m_flipHorizontallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); m_flipHorizontallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
@ -512,12 +516,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
}); });
m_windowMenu->addAction(m_showRigAction); m_windowMenu->addAction(m_showRigAction);
//m_showAnimationAction = new QAction(tr("Animation"), this); QMenu *dialogsMenu = m_windowMenu->addMenu(tr("Dialogs"));
//connect(m_showAnimationAction, &QAction::triggered, [=]() { connect(dialogsMenu, &QMenu::aboutToShow, [=]() {
// animationDocker->show(); dialogsMenu->clear();
// animationDocker->raise(); if (this->m_dialogs.empty()) {
//}); QAction *action = dialogsMenu->addAction(tr("None"));
//m_windowMenu->addAction(m_showAnimationAction); action->setEnabled(false);
return;
}
for (const auto &dialog: this->m_dialogs) {
QAction *action = dialogsMenu->addAction(dialog->windowTitle());
connect(action, &QAction::triggered, [=]() {
dialog->show();
dialog->raise();
});
}
});
m_showDebugDialogAction = new QAction(tr("Debug"), this); m_showDebugDialogAction = new QAction(tr("Debug"), this);
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog); connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
@ -764,6 +778,16 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
connect(m_document, &SkeletonDocument::resultRigChanged, this, &SkeletonDocumentWindow::updateRigWeightRenderWidget); connect(m_document, &SkeletonDocument::resultRigChanged, this, &SkeletonDocumentWindow::updateRigWeightRenderWidget);
//connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview); //connect(m_document, &SkeletonDocument::resultRigChanged, tetrapodPoseEditWidget, &TetrapodPoseEditWidget::updatePreview);
connect(m_document, &SkeletonDocument::poseAdded, this, [=](QUuid poseId) {
Q_UNUSED(poseId);
m_document->generatePosePreviews();
});
connect(m_document, &SkeletonDocument::poseParametersChanged, this, [=](QUuid poseId) {
Q_UNUSED(poseId);
m_document->generatePosePreviews();
});
connect(m_document, &SkeletonDocument::resultRigChanged, m_document, &SkeletonDocument::generatePosePreviews);
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady); connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
@ -1058,13 +1082,13 @@ void SkeletonDocumentWindow::showExportPreview()
{ {
if (nullptr == m_exportPreviewWidget) { if (nullptr == m_exportPreviewWidget) {
m_exportPreviewWidget = new ExportPreviewWidget(m_document, this); m_exportPreviewWidget = new ExportPreviewWidget(m_document, this);
m_exportPreviewWidget->setWindowFlags(Qt::Tool);
connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &SkeletonDocument::regenerateMesh); connect(m_exportPreviewWidget, &ExportPreviewWidget::regenerate, m_document, &SkeletonDocument::regenerateMesh);
connect(m_exportPreviewWidget, &ExportPreviewWidget::save, this, &SkeletonDocumentWindow::exportGltfResult); connect(m_exportPreviewWidget, &ExportPreviewWidget::save, this, &SkeletonDocumentWindow::exportGltfResult);
connect(m_document, &SkeletonDocument::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); connect(m_document, &SkeletonDocument::resultMeshChanged, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &SkeletonDocument::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner); connect(m_document, &SkeletonDocument::exportReady, m_exportPreviewWidget, &ExportPreviewWidget::checkSpinner);
connect(m_document, &SkeletonDocument::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); connect(m_document, &SkeletonDocument::resultTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview); connect(m_document, &SkeletonDocument::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
registerDialog(m_exportPreviewWidget);
} }
if (m_document->isPostProcessResultObsolete()) { if (m_document->isPostProcessResultObsolete()) {
m_document->postProcess(); m_document->postProcess();
@ -1145,3 +1169,13 @@ void SkeletonDocumentWindow::updateRigWeightRenderWidget()
m_rigWidget->rigWeightRenderWidget()->update(); m_rigWidget->rigWeightRenderWidget()->update();
} }
} }
void SkeletonDocumentWindow::registerDialog(QWidget *widget)
{
m_dialogs.push_back(widget);
}
void SkeletonDocumentWindow::unregisterDialog(QWidget *widget)
{
m_dialogs.erase(std::remove(m_dialogs.begin(), m_dialogs.end(), widget), m_dialogs.end());
}

View File

@ -12,6 +12,7 @@
#include "exportpreviewwidget.h" #include "exportpreviewwidget.h"
#include "rigwidget.h" #include "rigwidget.h"
#include "skeletonbonemark.h" #include "skeletonbonemark.h"
#include "posemanagewidget.h"
class SkeletonGraphicsWidget; class SkeletonGraphicsWidget;
@ -56,6 +57,8 @@ public slots:
void updateZlockButtonState(); void updateZlockButtonState();
void updateRadiusLockButtonState(); void updateRadiusLockButtonState();
void updateRigWeightRenderWidget(); void updateRigWeightRenderWidget();
void registerDialog(QWidget *widget);
void unregisterDialog(QWidget *widget);
private: private:
void initLockButton(QPushButton *button); void initLockButton(QPushButton *button);
void setCurrentFilename(const QString &filename); void setCurrentFilename(const QString &filename);
@ -65,12 +68,14 @@ private:
bool m_firstShow; bool m_firstShow;
bool m_documentSaved; bool m_documentSaved;
ExportPreviewWidget *m_exportPreviewWidget; ExportPreviewWidget *m_exportPreviewWidget;
std::vector<QWidget *> m_dialogs;
private: private:
QString m_currentFilename; QString m_currentFilename;
ModelWidget *m_modelRenderWidget; ModelWidget *m_modelRenderWidget;
SkeletonGraphicsWidget *m_graphicsWidget; SkeletonGraphicsWidget *m_graphicsWidget;
RigWidget *m_rigWidget; RigWidget *m_rigWidget;
PoseManageWidget *m_poseManageWidget;
QMenu *m_fileMenu; QMenu *m_fileMenu;
QAction *m_newWindowAction; QAction *m_newWindowAction;
@ -129,7 +134,6 @@ private:
QAction *m_showPartsListAction; QAction *m_showPartsListAction;
QAction *m_showDebugDialogAction; QAction *m_showDebugDialogAction;
QAction *m_showRigAction; QAction *m_showRigAction;
QAction *m_showAnimationAction;
QMenu *m_helpMenu; QMenu *m_helpMenu;
QAction *m_viewSourceAction; QAction *m_viewSourceAction;

View File

@ -158,7 +158,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
} }
QAction pasteAction(tr("Paste"), this); QAction pasteAction(tr("Paste"), this);
if (m_document->hasPastableContentInClipboard()) { if (m_document->hasPastableNodesInClipboard()) {
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste); connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
contextMenu.addAction(&pasteAction); contextMenu.addAction(&pasteAction);
} }
@ -1193,7 +1193,6 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
m_lastAddedX = unifiedMainPos.x(); m_lastAddedX = unifiedMainPos.x();
m_lastAddedY = unifiedMainPos.y(); m_lastAddedY = unifiedMainPos.y();
m_lastAddedZ = unifiedSidePos.x(); m_lastAddedZ = unifiedSidePos.x();
qDebug() << "Emit add node " << m_lastAddedX << m_lastAddedY << m_lastAddedZ;
emit addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_addFromNodeItem ? QUuid() : m_addFromNodeItem->id()); emit addNode(unifiedMainPos.x(), unifiedMainPos.y(), unifiedSidePos.x(), sceneRadiusToUnified(m_cursorNodeItem->radius()), nullptr == m_addFromNodeItem ? QUuid() : m_addFromNodeItem->id());
emit groupOperationAdded(); emit groupOperationAdded();
return true; return true;
@ -2158,7 +2157,7 @@ void SkeletonGraphicsWidget::copy()
if (nodeIdSet.empty()) if (nodeIdSet.empty())
return; return;
SkeletonSnapshot snapshot; SkeletonSnapshot snapshot;
m_document->toSnapshot(&snapshot, nodeIdSet); m_document->toSnapshot(&snapshot, nodeIdSet, SkeletonDocumentToSnapshotFor::Nodes);
QString snapshotXml; QString snapshotXml;
QXmlStreamWriter xmlStreamWriter(&snapshotXml); QXmlStreamWriter xmlStreamWriter(&snapshotXml);
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter); saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);

View File

@ -281,7 +281,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
ModelWidget *previewWidget = new ModelWidget; ModelWidget *previewWidget = new ModelWidget;
previewWidget->enableMove(false); previewWidget->enableMove(false);
previewWidget->enableZoom(false); previewWidget->enableZoom(false);
previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize); previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
previewWidget->setXRotation(partWidget->previewWidget()->xRot()); previewWidget->setXRotation(partWidget->previewWidget()->xRot());
previewWidget->setYRotation(partWidget->previewWidget()->yRot()); previewWidget->setYRotation(partWidget->previewWidget()->yRot());
previewWidget->setZRotation(partWidget->previewWidget()->zRot()); previewWidget->setZRotation(partWidget->previewWidget()->zRot());
@ -289,7 +289,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
layout->addWidget(previewWidget); layout->addWidget(previewWidget);
} else { } else {
QLabel *previewLabel = new QLabel; QLabel *previewLabel = new QLabel;
previewLabel->setFixedHeight(Theme::previewImageSize); previewLabel->setFixedHeight(Theme::partPreviewImageSize);
previewLabel->setStyleSheet("QLabel {color: " + Theme::red.name() + "}"); previewLabel->setStyleSheet("QLabel {color: " + Theme::red.name() + "}");
if (nullptr != component) { if (nullptr != component) {
previewLabel->setText(component->name); previewLabel->setText(component->name);

View File

@ -55,7 +55,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
m_previewWidget = new ModelWidget; m_previewWidget = new ModelWidget;
m_previewWidget->enableMove(false); m_previewWidget->enableMove(false);
m_previewWidget->enableZoom(false); m_previewWidget->enableZoom(false);
m_previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize); m_previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
QWidget *hrLightWidget = new QWidget; QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedHeight(1); hrLightWidget->setFixedHeight(1);
@ -99,7 +99,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
QWidget *backgroundWidget = new QWidget; QWidget *backgroundWidget = new QWidget;
backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
backgroundWidget->setFixedSize(preferredSize().width(), Theme::previewImageSize); backgroundWidget->setFixedSize(preferredSize().width(), Theme::partPreviewImageSize);
backgroundWidget->setObjectName("background"); backgroundWidget->setObjectName("background");
m_backgroundWidget = backgroundWidget; m_backgroundWidget = backgroundWidget;
backgroundWidget->setLayout(previewAndToolsLayout); backgroundWidget->setLayout(previewAndToolsLayout);
@ -238,7 +238,7 @@ ModelWidget *SkeletonPartWidget::previewWidget()
QSize SkeletonPartWidget::preferredSize() QSize SkeletonPartWidget::preferredSize()
{ {
return QSize(Theme::miniIconSize + Theme::previewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::previewImageSize + 6); return QSize(Theme::miniIconSize + Theme::partPreviewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::partPreviewImageSize + 6);
} }
void SkeletonPartWidget::updateAllButtons() void SkeletonPartWidget::updateAllButtons()

View File

@ -47,7 +47,7 @@ public:
static QSize preferredSize(); static QSize preferredSize();
ModelWidget *previewWidget(); ModelWidget *previewWidget();
protected: protected:
void mouseDoubleClickEvent(QMouseEvent *event); void mouseDoubleClickEvent(QMouseEvent *event) override;
public slots: public slots:
void showDeformSettingPopup(const QPoint &pos); void showDeformSettingPopup(const QPoint &pos);
void showColorSettingPopup(const QPoint &pos); void showColorSettingPopup(const QPoint &pos);

View File

@ -15,6 +15,7 @@ public:
std::map<QString, std::map<QString, QString>> parts; std::map<QString, std::map<QString, QString>> parts;
std::map<QString, std::map<QString, QString>> components; std::map<QString, std::map<QString, QString>> components;
std::map<QString, QString> rootComponent; std::map<QString, QString> rootComponent;
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>> poses; // std::pair<Pose attributes, Bone attributes>
public: public:
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString()); void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
}; };

View File

@ -91,6 +91,29 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
writer->writeEndElement(); writer->writeEndElement();
} }
writer->writeStartElement("poses");
std::vector<std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>>>::iterator poseIterator;
for (poseIterator = snapshot->poses.begin(); poseIterator != snapshot->poses.end(); poseIterator++) {
std::map<QString, QString>::iterator poseAttributeIterator;
writer->writeStartElement("pose");
for (poseAttributeIterator = poseIterator->first.begin(); poseAttributeIterator != poseIterator->first.end(); poseAttributeIterator++) {
writer->writeAttribute(poseAttributeIterator->first, poseAttributeIterator->second);
}
std::map<QString, std::map<QString, QString>>::iterator itemsIterator;
for (itemsIterator = poseIterator->second.begin(); itemsIterator != poseIterator->second.end(); itemsIterator++) {
std::map<QString, QString>::iterator parametersIterator;
writer->writeStartElement("parameter");
writer->writeAttribute("for", itemsIterator->first);
for (parametersIterator = itemsIterator->second.begin(); parametersIterator != itemsIterator->second.end();
parametersIterator++) {
writer->writeAttribute(parametersIterator->first, parametersIterator->second);
}
writer->writeEndElement();
}
writer->writeEndElement();
}
writer->writeEndElement();
writer->writeEndElement(); writer->writeEndElement();
writer->writeEndDocument(); writer->writeEndDocument();
@ -100,10 +123,14 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
{ {
std::stack<QString> componentStack; std::stack<QString> componentStack;
std::vector<QString> elementNameStack; std::vector<QString> elementNameStack;
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
while (!reader.atEnd()) { while (!reader.atEnd()) {
reader.readNext(); reader.readNext();
if (!reader.isStartElement() && !reader.isEndElement()) if (!reader.isStartElement() && !reader.isEndElement()) {
if (!reader.name().toString().isEmpty())
qDebug() << "Skip xml element:" << reader.name().toString() << " tokenType:" << reader.tokenType();
continue; continue;
}
QString baseName = reader.name().toString(); QString baseName = reader.name().toString();
if (reader.isStartElement()) if (reader.isStartElement())
elementNameStack.push_back(baseName); elementNameStack.push_back(baseName);
@ -114,6 +141,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
QString fullName = nameItems.join("."); QString fullName = nameItems.join(".");
if (reader.isEndElement()) if (reader.isEndElement())
elementNameStack.pop_back(); elementNameStack.pop_back();
//qDebug() << (reader.isStartElement() ? "<" : ">") << "fullName:" << fullName << "baseName:" << baseName;
if (reader.isStartElement()) { if (reader.isStartElement()) {
if (fullName == "canvas") { if (fullName == "canvas") {
foreach(const QXmlStreamAttribute &attr, reader.attributes()) { foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
@ -172,10 +200,29 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
if (!parentChildrenIds.isEmpty()) if (!parentChildrenIds.isEmpty())
parentChildrenIds += ","; parentChildrenIds += ",";
parentChildrenIds += componentId; parentChildrenIds += componentId;
} else if (fullName == "canvas.poses.pose") {
QString poseId = reader.attributes().value("id").toString();
if (poseId.isEmpty())
continue;
currentPose = decltype(currentPose)();
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
currentPose.first[attr.name().toString()] = attr.value().toString();
}
} else if (fullName == "canvas.poses.pose.parameter") {
QString forWhat = reader.attributes().value("for").toString();
if (forWhat.isEmpty())
continue;
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
if ("for" == attr.name().toString())
continue;
currentPose.second[forWhat][attr.name().toString()] = attr.value().toString();
}
} }
} else if (reader.isEndElement()) { } else if (reader.isEndElement()) {
if (fullName.startsWith("canvas.components.component")) { if (fullName.startsWith("canvas.components.component")) {
componentStack.pop(); componentStack.pop();
} else if (fullName == "canvas.poses.pose") {
snapshot->poses.push_back(currentPose);
} }
} }
} }

View File

@ -1,33 +0,0 @@
#ifndef TETRAPOD_POSE_EDIT_WIDGET_H
#define TETRAPOD_POSE_EDIT_WIDGET_H
#include <QWidget>
#include <map>
#include <QPointF>
#include "posepreviewmanager.h"
#include "tetrapodposer.h"
#include "skeletondocument.h"
enum class PopupWidgetType
{
PitchYawRoll,
Intersection
};
class TetrapodPoseEditWidget : public QWidget
{
Q_OBJECT
public:
TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent=nullptr);
~TetrapodPoseEditWidget();
public slots:
void updatePreview();
void showPopupAngleDialog(QString boneName, PopupWidgetType popupWidgetType, QPoint pos);
private:
const SkeletonDocument *m_document = nullptr;
PosePreviewManager *m_posePreviewManager = nullptr;
TetrapodPoser *m_poser = nullptr;
bool m_isPreviewDirty = false;
std::map<QString, std::map<QString, QString>> m_parameters;
};
#endif

View File

@ -33,8 +33,9 @@ int Theme::toolIconFontSize = 16;
int Theme::toolIconSize = 24; int Theme::toolIconSize = 24;
int Theme::miniIconFontSize = 9; int Theme::miniIconFontSize = 9;
int Theme::miniIconSize = 15; int Theme::miniIconSize = 15;
int Theme::previewImageSize = (Theme::miniIconSize * 3); int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
int Theme::previewImageRenderSize = 75; int Theme::posePreviewImageSize = 75;
int Theme::sidebarPreferredWidth = 200;
QtAwesome *Theme::awesome() QtAwesome *Theme::awesome()
{ {
@ -128,3 +129,22 @@ void Theme::initAwesomeToolButton(QPushButton *button)
Theme::initAwesomeToolButtonWithoutFont(button); Theme::initAwesomeToolButtonWithoutFont(button);
} }
QWidget *Theme::createHorizontalLineWidget()
{
QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedHeight(1);
hrLightWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
hrLightWidget->setContentsMargins(0, 0, 0, 0);
return hrLightWidget;
}
QWidget *Theme::createVerticalLineWidget()
{
QWidget *hrLightWidget = new QWidget;
hrLightWidget->setFixedWidth(1);
hrLightWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
hrLightWidget->setStyleSheet(QString("background-color: #565656;"));
hrLightWidget->setContentsMargins(0, 0, 0, 0);
return hrLightWidget;
}

View File

@ -30,12 +30,15 @@ public:
static std::map<QString, QString> nextSideColorNameMap; static std::map<QString, QString> nextSideColorNameMap;
static std::map<QString, QColor> sideColorNameToColorMap; static std::map<QString, QColor> sideColorNameToColorMap;
static QtAwesome *awesome(); static QtAwesome *awesome();
static QWidget *createHorizontalLineWidget();
static QWidget *createVerticalLineWidget();
static int toolIconFontSize; static int toolIconFontSize;
static int toolIconSize; static int toolIconSize;
static int previewImageRenderSize; static int posePreviewImageSize;
static int previewImageSize; static int partPreviewImageSize;
static int miniIconFontSize; static int miniIconFontSize;
static int miniIconSize; static int miniIconSize;
static int sidebarPreferredWidth;
public: public:
static void initAwesomeButton(QPushButton *button); static void initAwesomeButton(QPushButton *button);
static void initAwesomeLabel(QLabel *label); static void initAwesomeLabel(QLabel *label);