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
parent
f163522aa2
commit
e22a6de6cb
16
dust3d.pro
16
dust3d.pro
|
@ -179,8 +179,20 @@ HEADERS += src/posemeshcreator.h
|
|||
SOURCES += src/posepreviewmanager.cpp
|
||||
HEADERS += src/posepreviewmanager.h
|
||||
|
||||
SOURCES += src/tetrapodposeeditwidget.cpp
|
||||
HEADERS += src/tetrapodposeeditwidget.h
|
||||
SOURCES += src/poseeditwidget.cpp
|
||||
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
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <QOpenGLFunctions>
|
||||
#include "aboutwidget.h"
|
||||
#include "version.h"
|
||||
#include "dust3dutil.h"
|
||||
|
||||
AboutWidget::AboutWidget()
|
||||
{
|
||||
|
@ -16,5 +17,5 @@ AboutWidget::AboutWidget()
|
|||
setLayout(mainLayout);
|
||||
setFixedSize(QSize(350, 75));
|
||||
|
||||
setWindowTitle(APP_NAME);
|
||||
setWindowTitle(unifiedWindowTitle(tr("About")));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#ifndef ABOUT_WIDGET_H
|
||||
#define ABOUT_WIDGET_H
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
|
||||
class AboutWidget : public QWidget
|
||||
class AboutWidget : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <cmath>
|
||||
#include "dust3dutil.h"
|
||||
#include "version.h"
|
||||
|
||||
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
|
||||
{
|
||||
|
@ -51,3 +52,7 @@ QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal)
|
|||
return line - verticalOffset;
|
||||
}
|
||||
|
||||
QString unifiedWindowTitle(const QString &text)
|
||||
{
|
||||
return text + QObject::tr(" - ") + APP_NAME;
|
||||
}
|
||||
|
|
|
@ -17,5 +17,6 @@ void qNormalizeAngle(int &angle);
|
|||
QVector3D pointInHermiteCurve(float t, QVector3D p0, QVector3D m0, QVector3D p1, QVector3D m1);
|
||||
float angleInRangle360BetweenTwoVectors(QVector3D a, QVector3D b, QVector3D planeNormal);
|
||||
QVector3D projectLineOnPlane(QVector3D line, QVector3D planeNormal);
|
||||
QString unifiedWindowTitle(const QString &text);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
#include "aboutwidget.h"
|
||||
#include "version.h"
|
||||
#include "theme.h"
|
||||
#include "dust3dutil.h"
|
||||
|
||||
ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
QDialog(parent),
|
||||
m_document(document),
|
||||
m_previewLabel(nullptr),
|
||||
m_spinnerWidget(nullptr)
|
||||
|
@ -33,6 +34,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
|||
m_saveButton = new QPushButton(tr("Save"));
|
||||
connect(m_saveButton, &QPushButton::clicked, this, &ExportPreviewWidget::save);
|
||||
m_saveButton->hide();
|
||||
m_saveButton->setDefault(true);
|
||||
|
||||
QComboBox *exportFormatSelectBox = new QComboBox;
|
||||
exportFormatSelectBox->addItem(tr("glTF"));
|
||||
|
@ -59,11 +61,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
|||
renderLayout->setContentsMargins(20, 0, 20, 0);
|
||||
renderLayout->addWidget(m_textureRenderWidget);
|
||||
|
||||
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);
|
||||
QWidget *hrLightWidget = Theme::createHorizontalLineWidget();
|
||||
|
||||
QHBoxLayout *topLayout = new QHBoxLayout;
|
||||
topLayout->setSpacing(0);
|
||||
|
@ -86,7 +84,7 @@ ExportPreviewWidget::ExportPreviewWidget(SkeletonDocument *document, QWidget *pa
|
|||
m_spinnerWidget->setNumberOfLines(12);
|
||||
m_spinnerWidget->hide();
|
||||
|
||||
setWindowTitle(tr("Export") + tr(" - ") + APP_NAME);
|
||||
setWindowTitle(unifiedWindowTitle(tr("Export")));
|
||||
|
||||
emit updateTexturePreview();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#ifndef EXPORT_PREVIEW_WIDGET_H
|
||||
#define EXPORT_PREVIEW_WIDGET_H
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
#include <QImage>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
|
@ -9,7 +9,7 @@
|
|||
#include "waitingspinnerwidget.h"
|
||||
#include "skeletondocument.h"
|
||||
|
||||
class ExportPreviewWidget : public QWidget
|
||||
class ExportPreviewWidget : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <QCloseEvent>
|
||||
#include <QKeyEvent>
|
||||
#include "version.h"
|
||||
#include "dust3dutil.h"
|
||||
|
||||
LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
||||
QDialog(parent)
|
||||
|
@ -40,7 +41,7 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
|||
|
||||
resize(400, 300);
|
||||
|
||||
setWindowTitle(tr("Debug") + tr(" - ") + APP_NAME);
|
||||
setWindowTitle(unifiedWindowTitle(tr("Debug")));
|
||||
|
||||
hide();
|
||||
}
|
||||
|
|
|
@ -434,7 +434,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
|
|||
auto findCachedMesh = m_cacheContext->componentCombinableMeshs.find(componentId);
|
||||
if (findCachedMesh != m_cacheContext->componentCombinableMeshs.end() &&
|
||||
nullptr != findCachedMesh->second) {
|
||||
qDebug() << "Component mesh cache used:" << componentId;
|
||||
//qDebug() << "Component mesh cache used:" << componentId;
|
||||
return cloneCombinableMesh(findCachedMesh->second);
|
||||
}
|
||||
}
|
||||
|
@ -519,7 +519,7 @@ void *MeshGenerator::combineComponentMesh(QString componentId, bool *inverse)
|
|||
}
|
||||
}
|
||||
if (seamVerticesNum > 0) {
|
||||
qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
|
||||
//qDebug() << "smoothSeamFactor:" << smoothSeamFactor << "seamVerticesIndicies.size():" << seamVerticesNum;
|
||||
meshlite_smooth_vertices(m_meshliteContext, meshIdForSmooth, smoothSeamFactor, seamVerticesIndicies, seamVerticesNum);
|
||||
}
|
||||
delete[] seamVerticesIndicies;
|
||||
|
|
|
@ -1,21 +1,32 @@
|
|||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QFormLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QMenu>
|
||||
#include <QWidgetAction>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include "theme.h"
|
||||
#include "tetrapodposeeditwidget.h"
|
||||
#include "poseeditwidget.h"
|
||||
#include "floatnumberwidget.h"
|
||||
#include "version.h"
|
||||
|
||||
TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
PoseEditWidget::PoseEditWidget(const SkeletonDocument *document, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
m_document(document)
|
||||
{
|
||||
m_posePreviewManager = new PosePreviewManager();
|
||||
connect(m_posePreviewManager, &PosePreviewManager::renderDone, [=]() {
|
||||
if (m_closed) {
|
||||
close();
|
||||
return;
|
||||
}
|
||||
if (m_isPreviewDirty)
|
||||
updatePreview();
|
||||
});
|
||||
connect(m_posePreviewManager, &PosePreviewManager::resultPreviewMeshChanged, [=]() {
|
||||
m_previewWidget->updateMesh(m_posePreviewManager->takeResultPreviewMesh());
|
||||
});
|
||||
|
||||
std::map<QString, std::tuple<QPushButton *, PopupWidgetType>> buttons;
|
||||
buttons["Head"] = std::make_tuple(new QPushButton(tr("Head")), PopupWidgetType::PitchYawRoll);
|
||||
|
@ -61,36 +72,117 @@ TetrapodPoseEditWidget::TetrapodPoseEditWidget(const SkeletonDocument *document,
|
|||
|
||||
QFont buttonFont;
|
||||
buttonFont.setWeight(QFont::Light);
|
||||
buttonFont.setPixelSize(7);
|
||||
buttonFont.setPixelSize(9);
|
||||
buttonFont.setBold(false);
|
||||
for (const auto &item: buttons) {
|
||||
QString boneName = item.first;
|
||||
QPushButton *buttonWidget = std::get<0>(item.second);
|
||||
PopupWidgetType widgetType = std::get<1>(item.second);
|
||||
buttonWidget->setFont(buttonFont);
|
||||
buttonWidget->setMaximumWidth(45);
|
||||
buttonWidget->setMaximumWidth(55);
|
||||
connect(buttonWidget, &QPushButton::clicked, [this, boneName, widgetType]() {
|
||||
emit showPopupAngleDialog(boneName, widgetType, mapFromGlobal(QCursor::pos()));
|
||||
});
|
||||
}
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addLayout(marksContainerLayout);
|
||||
layout->addLayout(lowerMarksContainerLayout);
|
||||
layout->addStretch();
|
||||
m_previewWidget = new ModelWidget(this);
|
||||
m_previewWidget->setMinimumSize(128, 128);
|
||||
m_previewWidget->resize(384, 384);
|
||||
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;
|
||||
}
|
||||
|
||||
void TetrapodPoseEditWidget::updatePreview()
|
||||
void PoseEditWidget::updatePreview()
|
||||
{
|
||||
if (m_closed)
|
||||
return;
|
||||
|
||||
if (m_posePreviewManager->isRendering()) {
|
||||
m_isPreviewDirty = true;
|
||||
return;
|
||||
|
@ -105,14 +197,37 @@ void TetrapodPoseEditWidget::updatePreview()
|
|||
return;
|
||||
}
|
||||
|
||||
delete m_poser;
|
||||
m_poser = new TetrapodPoser(*rigBones);
|
||||
m_poser->parameters() = m_parameters;
|
||||
m_poser->commit();
|
||||
m_posePreviewManager->postUpdate(*m_poser, m_document->currentRiggedResultContext(), *rigWeights);
|
||||
TetrapodPoser *poser = new TetrapodPoser(*rigBones);
|
||||
poser->parameters() = m_parameters;
|
||||
poser->commit();
|
||||
m_posePreviewManager->postUpdate(*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;
|
||||
|
||||
|
@ -125,13 +240,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
|||
pitchWidget->setItemName(tr("Pitch"));
|
||||
pitchWidget->setRange(-180, 180);
|
||||
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);
|
||||
updatePreview();
|
||||
emit parametersAdjusted();
|
||||
});
|
||||
QPushButton *pitchEraser = new QPushButton(QChar(fa::eraser));
|
||||
Theme::initAwesomeMiniButton(pitchEraser);
|
||||
connect(pitchEraser, &QPushButton::clicked, [=]() {
|
||||
connect(pitchEraser, &QPushButton::clicked, this, [=]() {
|
||||
pitchWidget->setValue(0.0);
|
||||
});
|
||||
QHBoxLayout *pitchLayout = new QHBoxLayout;
|
||||
|
@ -143,13 +258,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
|||
yawWidget->setItemName(tr("Yaw"));
|
||||
yawWidget->setRange(-180, 180);
|
||||
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);
|
||||
updatePreview();
|
||||
emit parametersAdjusted();
|
||||
});
|
||||
QPushButton *yawEraser = new QPushButton(QChar(fa::eraser));
|
||||
Theme::initAwesomeMiniButton(yawEraser);
|
||||
connect(yawEraser, &QPushButton::clicked, [=]() {
|
||||
connect(yawEraser, &QPushButton::clicked, this, [=]() {
|
||||
yawWidget->setValue(0.0);
|
||||
});
|
||||
QHBoxLayout *yawLayout = new QHBoxLayout;
|
||||
|
@ -161,13 +276,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
|||
rollWidget->setItemName(tr("Roll"));
|
||||
rollWidget->setRange(-180, 180);
|
||||
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);
|
||||
updatePreview();
|
||||
emit parametersAdjusted();
|
||||
});
|
||||
QPushButton *rollEraser = new QPushButton(QChar(fa::eraser));
|
||||
Theme::initAwesomeMiniButton(rollEraser);
|
||||
connect(rollEraser, &QPushButton::clicked, [=]() {
|
||||
connect(rollEraser, &QPushButton::clicked, this, [=]() {
|
||||
rollWidget->setValue(0.0);
|
||||
});
|
||||
QHBoxLayout *rollLayout = new QHBoxLayout;
|
||||
|
@ -179,13 +294,13 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
|||
intersectionWidget->setItemName(tr("Intersection"));
|
||||
intersectionWidget->setRange(-180, 180);
|
||||
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);
|
||||
updatePreview();
|
||||
emit parametersAdjusted();
|
||||
});
|
||||
QPushButton *intersectionEraser = new QPushButton(QChar(fa::eraser));
|
||||
Theme::initAwesomeMiniButton(intersectionEraser);
|
||||
connect(intersectionEraser, &QPushButton::clicked, [=]() {
|
||||
connect(intersectionEraser, &QPushButton::clicked, this, [=]() {
|
||||
intersectionWidget->setValue(0.0);
|
||||
});
|
||||
QHBoxLayout *intersectionLayout = new QHBoxLayout;
|
||||
|
@ -201,5 +316,40 @@ void TetrapodPoseEditWidget::showPopupAngleDialog(QString boneName, PopupWidgetT
|
|||
|
||||
popupMenu.addAction(&action);
|
||||
|
||||
m_openedMenuCount++;
|
||||
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();
|
||||
}
|
|
@ -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
|
|
@ -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(©Action, &QAction::triggered, this, &PoseListWidget::copy);
|
||||
contextMenu.addAction(©Action);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -49,12 +49,11 @@ void PosePreviewManager::poseMeshReady()
|
|||
delete m_previewMesh;
|
||||
m_previewMesh = m_poseMeshCreator->takeResultMesh();
|
||||
|
||||
emit resultPreviewMeshChanged();
|
||||
|
||||
qDebug() << "Pose mesh generation done";
|
||||
|
||||
delete m_poseMeshCreator;
|
||||
m_poseMeshCreator = nullptr;
|
||||
|
||||
emit resultPreviewMeshChanged();
|
||||
emit renderDone();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -16,7 +16,7 @@ public:
|
|||
const std::vector<AutoRiggerBone> &bones() const;
|
||||
const std::vector<JointNode> &resultNodes() const;
|
||||
std::map<QString, std::map<QString, QString>> ¶meters();
|
||||
void commit();
|
||||
virtual void commit();
|
||||
void reset();
|
||||
protected:
|
||||
std::vector<AutoRiggerBone> m_bones;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -51,7 +51,8 @@ SkeletonDocument::SkeletonDocument() :
|
|||
m_resultRigBones(nullptr),
|
||||
m_resultRigWeights(nullptr),
|
||||
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;
|
||||
partMap[partId].nodeIds.push_back(node.id);
|
||||
|
||||
qDebug() << "Add node " << node.id << x << y << z;
|
||||
|
||||
emit nodeAdded(node.id);
|
||||
|
||||
if (nullptr != fromNode) {
|
||||
|
@ -306,6 +305,66 @@ QUuid SkeletonDocument::createNode(float x, float y, float z, float radius, QUui
|
|||
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 SkeletonNode *firstNode = nullptr;
|
||||
|
@ -446,6 +505,14 @@ const SkeletonComponent *SkeletonDocument::findComponent(QUuid componentId) cons
|
|||
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)
|
||||
{
|
||||
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;
|
||||
std::set<QUuid> limitComponentIds;
|
||||
for (const auto &nodeId: limitNodeIds) {
|
||||
const SkeletonNode *node = findNode(nodeId);
|
||||
if (!node)
|
||||
continue;
|
||||
const SkeletonPart *part = findPart(node->partId);
|
||||
if (!part)
|
||||
continue;
|
||||
limitPartIds.insert(node->partId);
|
||||
const SkeletonComponent *component = findComponent(part->componentId);
|
||||
while (component) {
|
||||
limitComponentIds.insert(component->id);
|
||||
if (component->id.isNull())
|
||||
break;
|
||||
component = findComponent(component->parentId);
|
||||
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
|
||||
SkeletonDocumentToSnapshotFor::Nodes == forWhat) {
|
||||
std::set<QUuid> limitPartIds;
|
||||
std::set<QUuid> limitComponentIds;
|
||||
for (const auto &nodeId: limitNodeIds) {
|
||||
const SkeletonNode *node = findNode(nodeId);
|
||||
if (!node)
|
||||
continue;
|
||||
const SkeletonPart *part = findPart(node->partId);
|
||||
if (!part)
|
||||
continue;
|
||||
limitPartIds.insert(node->partId);
|
||||
const SkeletonComponent *component = findComponent(part->componentId);
|
||||
while (component) {
|
||||
limitComponentIds.insert(component->id);
|
||||
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 (!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());
|
||||
if (SkeletonDocumentToSnapshotFor::Document == forWhat ||
|
||||
SkeletonDocumentToSnapshotFor::Poses == forWhat) {
|
||||
for (const auto &poseId: poseIdList) {
|
||||
if (!limitPoseIds.empty() && limitPoseIds.find(poseId) == limitPoseIds.end())
|
||||
continue;
|
||||
auto findPoseResult = poseMap.find(poseId);
|
||||
if (findPoseResult == poseMap.end()) {
|
||||
qDebug() << "Find pose failed:" << poseId;
|
||||
continue;
|
||||
}
|
||||
auto &poseIt = *findPoseResult;
|
||||
std::map<QString, QString> pose;
|
||||
pose["id"] = poseIt.second.id.toString();
|
||||
if (!poseIt.second.name.isEmpty())
|
||||
pose["name"] = poseIt.second.name;
|
||||
snapshot->poses.push_back(std::make_pair(pose, poseIt.second.parameters));
|
||||
}
|
||||
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;
|
||||
if (SkeletonDocumentToSnapshotFor::Document == forWhat) {
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -921,11 +1011,11 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
|||
const auto &smoothSeamIt = componentKv.second.find("smoothSeam");
|
||||
if (smoothSeamIt != componentKv.second.end())
|
||||
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) {
|
||||
QUuid partId = oldNewIdMap[QUuid(linkData)];
|
||||
component.linkToPartId = partId;
|
||||
qDebug() << "Add part:" << partId << " from component:" << component.id;
|
||||
//qDebug() << "Add part:" << partId << " from component:" << component.id;
|
||||
partMap[partId].componentId = component.id;
|
||||
if (inversePartIds.find(partId) != inversePartIds.end())
|
||||
component.inverse = true;
|
||||
|
@ -941,7 +1031,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
|||
QUuid componentId = oldNewIdMap[QUuid(childId)];
|
||||
if (componentMap.find(componentId) == componentMap.end())
|
||||
continue;
|
||||
qDebug() << "Add root component:" << componentId;
|
||||
//qDebug() << "Add root component:" << componentId;
|
||||
rootComponent.addChild(componentId);
|
||||
}
|
||||
}
|
||||
|
@ -955,11 +1045,22 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
|||
QUuid childComponentId = oldNewIdMap[QUuid(childId)];
|
||||
if (componentMap.find(childComponentId) == componentMap.end())
|
||||
continue;
|
||||
qDebug() << "Add child component:" << childComponentId << "to" << componentId;
|
||||
//qDebug() << "Add child component:" << childComponentId << "to" << componentId;
|
||||
componentMap[componentId].addChild(childComponentId);
|
||||
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) {
|
||||
emit nodeAdded(nodeIt);
|
||||
|
@ -989,6 +1090,9 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot, bool fr
|
|||
for (const auto &edgeIt: newAddedEdgeIds) {
|
||||
emit checkEdge(edgeIt);
|
||||
}
|
||||
|
||||
if (!snapshot.poses.empty())
|
||||
emit poseListChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::reset()
|
||||
|
@ -1001,6 +1105,8 @@ void SkeletonDocument::reset()
|
|||
edgeMap.clear();
|
||||
partMap.clear();
|
||||
componentMap.clear();
|
||||
poseMap.clear();
|
||||
poseIdList.clear();
|
||||
rootComponent = SkeletonComponent();
|
||||
emit cleanup();
|
||||
emit skeletonChanged();
|
||||
|
@ -1954,12 +2060,23 @@ void SkeletonDocument::paste()
|
|||
}
|
||||
}
|
||||
|
||||
bool SkeletonDocument::hasPastableContentInClipboard() const
|
||||
bool SkeletonDocument::hasPastableNodesInClipboard() const
|
||||
{
|
||||
const QClipboard *clipboard = QApplication::clipboard();
|
||||
const QMimeData *mimeData = clipboard->mimeData();
|
||||
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 false;
|
||||
|
@ -2337,3 +2454,63 @@ const MeshResultContext &SkeletonDocument::currentRiggedResultContext() const
|
|||
{
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "skeletonbonemark.h"
|
||||
#include "riggenerator.h"
|
||||
#include "rigtype.h"
|
||||
#include "posepreviewsgenerator.h"
|
||||
|
||||
class SkeletonNode
|
||||
{
|
||||
|
@ -345,6 +346,43 @@ private:
|
|||
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
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -406,6 +444,12 @@ signals:
|
|||
void checkEdge(QUuid edgeId);
|
||||
void optionsChanged();
|
||||
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
|
||||
float originX;
|
||||
float originY;
|
||||
|
@ -428,10 +472,14 @@ public:
|
|||
std::map<QUuid, SkeletonNode> nodeMap;
|
||||
std::map<QUuid, SkeletonEdge> edgeMap;
|
||||
std::map<QUuid, SkeletonComponent> componentMap;
|
||||
std::map<QUuid, SkeletonPose> poseMap;
|
||||
std::vector<QUuid> poseIdList;
|
||||
SkeletonComponent rootComponent;
|
||||
QImage turnaround;
|
||||
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 addFromSnapshot(const SkeletonSnapshot &snapshot, bool fromPaste=true);
|
||||
const SkeletonNode *findNode(QUuid nodeId) const;
|
||||
|
@ -441,6 +489,7 @@ public:
|
|||
const SkeletonComponent *findComponent(QUuid componentId) const;
|
||||
const SkeletonComponent *findComponentParent(QUuid componentId) const;
|
||||
QUuid findComponentParentId(QUuid componentId) const;
|
||||
const SkeletonPose *findPose(QUuid poseId) const;
|
||||
MeshLoader *takeResultMesh();
|
||||
MeshLoader *takeResultTextureMesh();
|
||||
MeshLoader *takeResultRigWeightMesh();
|
||||
|
@ -448,7 +497,8 @@ public:
|
|||
const std::map<int, AutoRiggerVertexWeights> *resultRigWeights() const;
|
||||
void updateTurnaround(const QImage &image);
|
||||
void setSharedContextWidget(QOpenGLWidget *sharedContextWidget);
|
||||
bool hasPastableContentInClipboard() const;
|
||||
bool hasPastableNodesInClipboard() const;
|
||||
bool hasPastablePosesInClipboard() const;
|
||||
bool undoable() const;
|
||||
bool redoable() const;
|
||||
bool isNodeEditable(QUuid nodeId) const;
|
||||
|
@ -489,6 +539,8 @@ public slots:
|
|||
void ambientOcclusionTextureReady();
|
||||
void generateRig();
|
||||
void rigReady();
|
||||
void generatePosePreviews();
|
||||
void posePreviewsReady();
|
||||
void setPartLockState(QUuid partId, bool locked);
|
||||
void setPartVisibleState(QUuid partId, bool visible);
|
||||
void setPartSubdivState(QUuid partId, bool subdived);
|
||||
|
@ -543,6 +595,10 @@ public slots:
|
|||
void disableAllPositionRelatedLocks();
|
||||
void toggleSmoothNormal();
|
||||
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:
|
||||
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());
|
||||
|
@ -583,6 +639,7 @@ private: // need initialize
|
|||
std::map<int, AutoRiggerVertexWeights> *m_resultRigWeights;
|
||||
bool m_isRigObsolete;
|
||||
MeshResultContext *m_riggedResultContext;
|
||||
PosePreviewsGenerator *m_posePreviewsGenerator;
|
||||
private:
|
||||
static unsigned long m_maxSnapshot;
|
||||
std::deque<SkeletonHistoryItem> m_undoItems;
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
#include "skeletonparttreewidget.h"
|
||||
#include "rigwidget.h"
|
||||
#include "markiconcreator.h"
|
||||
#include "tetrapodposeeditwidget.h"
|
||||
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialX = 16;
|
||||
int SkeletonDocumentWindow::m_modelRenderWidgetInitialY = 16;
|
||||
|
@ -55,7 +54,7 @@ void SkeletonDocumentWindow::showAcknowlegements()
|
|||
{
|
||||
if (!g_acknowlegementsWidget) {
|
||||
g_acknowlegementsWidget = new QTextBrowser;
|
||||
g_acknowlegementsWidget->setWindowTitle(APP_NAME);
|
||||
g_acknowlegementsWidget->setWindowTitle(unifiedWindowTitle(tr("Acknowlegements")));
|
||||
g_acknowlegementsWidget->setMinimumSize(QSize(400, 300));
|
||||
QFile file(":/ACKNOWLEDGEMENTS.html");
|
||||
file.open(QFile::ReadOnly | QFile::Text);
|
||||
|
@ -71,7 +70,7 @@ void SkeletonDocumentWindow::showContributors()
|
|||
{
|
||||
if (!g_contributorsWidget) {
|
||||
g_contributorsWidget = new QTextBrowser;
|
||||
g_contributorsWidget->setWindowTitle(APP_NAME);
|
||||
g_contributorsWidget->setWindowTitle(unifiedWindowTitle(tr("Contributors")));
|
||||
g_contributorsWidget->setMinimumSize(QSize(400, 300));
|
||||
QFile authors(":/AUTHORS");
|
||||
authors.open(QFile::ReadOnly | QFile::Text);
|
||||
|
@ -225,16 +224,21 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
updateRigWeightRenderWidget();
|
||||
});
|
||||
|
||||
//QDockWidget *animationDocker = new QDockWidget(tr("Animation"), this);
|
||||
//animationDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
//TetrapodPoseEditWidget *tetrapodPoseEditWidget = new TetrapodPoseEditWidget(m_document, animationDocker);
|
||||
//animationDocker->setWidget(tetrapodPoseEditWidget);
|
||||
//AnimationListWidget *animationListWidget = new AnimationListWidget(m_document, animationDocker);
|
||||
//animationDocker->setWidget(animationListWidget);
|
||||
//addDockWidget(Qt::RightDockWidgetArea, animationDocker);
|
||||
QDockWidget *poseDocker = new QDockWidget(tr("Poses"), this);
|
||||
poseDocker->setAllowedAreas(Qt::RightDockWidgetArea);
|
||||
PoseManageWidget *poseManageWidget = new PoseManageWidget(m_document, poseDocker);
|
||||
poseDocker->setWidget(poseManageWidget);
|
||||
connect(poseManageWidget, &PoseManageWidget::registerDialog, this, &SkeletonDocumentWindow::registerDialog);
|
||||
connect(poseManageWidget, &PoseManageWidget::unregisterDialog, this, &SkeletonDocumentWindow::unregisterDialog);
|
||||
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(rigDocker, animationDocker);
|
||||
tabifyDockWidget(rigDocker, poseDocker);
|
||||
|
||||
partTreeDocker->raise();
|
||||
|
||||
|
@ -447,7 +451,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_connectAction->setEnabled(m_graphicsWidget->hasTwoDisconnectedNodesSelection());
|
||||
m_cutAction->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_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||
m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||
|
@ -512,12 +516,22 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
});
|
||||
m_windowMenu->addAction(m_showRigAction);
|
||||
|
||||
//m_showAnimationAction = new QAction(tr("Animation"), this);
|
||||
//connect(m_showAnimationAction, &QAction::triggered, [=]() {
|
||||
// animationDocker->show();
|
||||
// animationDocker->raise();
|
||||
//});
|
||||
//m_windowMenu->addAction(m_showAnimationAction);
|
||||
QMenu *dialogsMenu = m_windowMenu->addMenu(tr("Dialogs"));
|
||||
connect(dialogsMenu, &QMenu::aboutToShow, [=]() {
|
||||
dialogsMenu->clear();
|
||||
if (this->m_dialogs.empty()) {
|
||||
QAction *action = dialogsMenu->addAction(tr("None"));
|
||||
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);
|
||||
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, 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);
|
||||
|
||||
|
@ -1058,13 +1082,13 @@ void SkeletonDocumentWindow::showExportPreview()
|
|||
{
|
||||
if (nullptr == m_exportPreviewWidget) {
|
||||
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::save, this, &SkeletonDocumentWindow::exportGltfResult);
|
||||
connect(m_document, &SkeletonDocument::resultMeshChanged, 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::resultBakedTextureChanged, m_exportPreviewWidget, &ExportPreviewWidget::updateTexturePreview);
|
||||
registerDialog(m_exportPreviewWidget);
|
||||
}
|
||||
if (m_document->isPostProcessResultObsolete()) {
|
||||
m_document->postProcess();
|
||||
|
@ -1145,3 +1169,13 @@ void SkeletonDocumentWindow::updateRigWeightRenderWidget()
|
|||
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());
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include "exportpreviewwidget.h"
|
||||
#include "rigwidget.h"
|
||||
#include "skeletonbonemark.h"
|
||||
#include "posemanagewidget.h"
|
||||
|
||||
class SkeletonGraphicsWidget;
|
||||
|
||||
|
@ -56,6 +57,8 @@ public slots:
|
|||
void updateZlockButtonState();
|
||||
void updateRadiusLockButtonState();
|
||||
void updateRigWeightRenderWidget();
|
||||
void registerDialog(QWidget *widget);
|
||||
void unregisterDialog(QWidget *widget);
|
||||
private:
|
||||
void initLockButton(QPushButton *button);
|
||||
void setCurrentFilename(const QString &filename);
|
||||
|
@ -65,12 +68,14 @@ private:
|
|||
bool m_firstShow;
|
||||
bool m_documentSaved;
|
||||
ExportPreviewWidget *m_exportPreviewWidget;
|
||||
std::vector<QWidget *> m_dialogs;
|
||||
private:
|
||||
QString m_currentFilename;
|
||||
|
||||
ModelWidget *m_modelRenderWidget;
|
||||
SkeletonGraphicsWidget *m_graphicsWidget;
|
||||
RigWidget *m_rigWidget;
|
||||
PoseManageWidget *m_poseManageWidget;
|
||||
|
||||
QMenu *m_fileMenu;
|
||||
QAction *m_newWindowAction;
|
||||
|
@ -129,7 +134,6 @@ private:
|
|||
QAction *m_showPartsListAction;
|
||||
QAction *m_showDebugDialogAction;
|
||||
QAction *m_showRigAction;
|
||||
QAction *m_showAnimationAction;
|
||||
|
||||
QMenu *m_helpMenu;
|
||||
QAction *m_viewSourceAction;
|
||||
|
|
|
@ -158,7 +158,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
}
|
||||
|
||||
QAction pasteAction(tr("Paste"), this);
|
||||
if (m_document->hasPastableContentInClipboard()) {
|
||||
if (m_document->hasPastableNodesInClipboard()) {
|
||||
connect(&pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||||
contextMenu.addAction(&pasteAction);
|
||||
}
|
||||
|
@ -1193,7 +1193,6 @@ bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
|
|||
m_lastAddedX = unifiedMainPos.x();
|
||||
m_lastAddedY = unifiedMainPos.y();
|
||||
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 groupOperationAdded();
|
||||
return true;
|
||||
|
@ -2158,7 +2157,7 @@ void SkeletonGraphicsWidget::copy()
|
|||
if (nodeIdSet.empty())
|
||||
return;
|
||||
SkeletonSnapshot snapshot;
|
||||
m_document->toSnapshot(&snapshot, nodeIdSet);
|
||||
m_document->toSnapshot(&snapshot, nodeIdSet, SkeletonDocumentToSnapshotFor::Nodes);
|
||||
QString snapshotXml;
|
||||
QXmlStreamWriter xmlStreamWriter(&snapshotXml);
|
||||
saveSkeletonToXmlStream(&snapshot, &xmlStreamWriter);
|
||||
|
|
|
@ -281,7 +281,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
|
|||
ModelWidget *previewWidget = new ModelWidget;
|
||||
previewWidget->enableMove(false);
|
||||
previewWidget->enableZoom(false);
|
||||
previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize);
|
||||
previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
|
||||
previewWidget->setXRotation(partWidget->previewWidget()->xRot());
|
||||
previewWidget->setYRotation(partWidget->previewWidget()->yRot());
|
||||
previewWidget->setZRotation(partWidget->previewWidget()->zRot());
|
||||
|
@ -289,7 +289,7 @@ void SkeletonPartTreeWidget::showContextMenu(const QPoint &pos)
|
|||
layout->addWidget(previewWidget);
|
||||
} else {
|
||||
QLabel *previewLabel = new QLabel;
|
||||
previewLabel->setFixedHeight(Theme::previewImageSize);
|
||||
previewLabel->setFixedHeight(Theme::partPreviewImageSize);
|
||||
previewLabel->setStyleSheet("QLabel {color: " + Theme::red.name() + "}");
|
||||
if (nullptr != component) {
|
||||
previewLabel->setText(component->name);
|
||||
|
|
|
@ -55,7 +55,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
|
|||
m_previewWidget = new ModelWidget;
|
||||
m_previewWidget->enableMove(false);
|
||||
m_previewWidget->enableZoom(false);
|
||||
m_previewWidget->setFixedSize(Theme::previewImageSize, Theme::previewImageSize);
|
||||
m_previewWidget->setFixedSize(Theme::partPreviewImageSize, Theme::partPreviewImageSize);
|
||||
|
||||
QWidget *hrLightWidget = new QWidget;
|
||||
hrLightWidget->setFixedHeight(1);
|
||||
|
@ -99,7 +99,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
|
|||
|
||||
QWidget *backgroundWidget = new QWidget;
|
||||
backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
backgroundWidget->setFixedSize(preferredSize().width(), Theme::previewImageSize);
|
||||
backgroundWidget->setFixedSize(preferredSize().width(), Theme::partPreviewImageSize);
|
||||
backgroundWidget->setObjectName("background");
|
||||
m_backgroundWidget = backgroundWidget;
|
||||
backgroundWidget->setLayout(previewAndToolsLayout);
|
||||
|
@ -238,7 +238,7 @@ ModelWidget *SkeletonPartWidget::previewWidget()
|
|||
|
||||
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()
|
||||
|
|
|
@ -47,7 +47,7 @@ public:
|
|||
static QSize preferredSize();
|
||||
ModelWidget *previewWidget();
|
||||
protected:
|
||||
void mouseDoubleClickEvent(QMouseEvent *event);
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
public slots:
|
||||
void showDeformSettingPopup(const QPoint &pos);
|
||||
void showColorSettingPopup(const QPoint &pos);
|
||||
|
|
|
@ -15,6 +15,7 @@ public:
|
|||
std::map<QString, std::map<QString, QString>> parts;
|
||||
std::map<QString, std::map<QString, QString>> components;
|
||||
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:
|
||||
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile, const QString &partId=QString());
|
||||
};
|
||||
|
|
|
@ -91,6 +91,29 @@ void saveSkeletonToXmlStream(SkeletonSnapshot *snapshot, QXmlStreamWriter *write
|
|||
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->writeEndDocument();
|
||||
|
@ -100,10 +123,14 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
|||
{
|
||||
std::stack<QString> componentStack;
|
||||
std::vector<QString> elementNameStack;
|
||||
std::pair<std::map<QString, QString>, std::map<QString, std::map<QString, QString>>> currentPose;
|
||||
while (!reader.atEnd()) {
|
||||
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;
|
||||
}
|
||||
QString baseName = reader.name().toString();
|
||||
if (reader.isStartElement())
|
||||
elementNameStack.push_back(baseName);
|
||||
|
@ -114,6 +141,7 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
|||
QString fullName = nameItems.join(".");
|
||||
if (reader.isEndElement())
|
||||
elementNameStack.pop_back();
|
||||
//qDebug() << (reader.isStartElement() ? "<" : ">") << "fullName:" << fullName << "baseName:" << baseName;
|
||||
if (reader.isStartElement()) {
|
||||
if (fullName == "canvas") {
|
||||
foreach(const QXmlStreamAttribute &attr, reader.attributes()) {
|
||||
|
@ -172,10 +200,29 @@ void loadSkeletonFromXmlStream(SkeletonSnapshot *snapshot, QXmlStreamReader &rea
|
|||
if (!parentChildrenIds.isEmpty())
|
||||
parentChildrenIds += ",";
|
||||
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()) {
|
||||
if (fullName.startsWith("canvas.components.component")) {
|
||||
componentStack.pop();
|
||||
} else if (fullName == "canvas.poses.pose") {
|
||||
snapshot->poses.push_back(currentPose);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -33,8 +33,9 @@ int Theme::toolIconFontSize = 16;
|
|||
int Theme::toolIconSize = 24;
|
||||
int Theme::miniIconFontSize = 9;
|
||||
int Theme::miniIconSize = 15;
|
||||
int Theme::previewImageSize = (Theme::miniIconSize * 3);
|
||||
int Theme::previewImageRenderSize = 75;
|
||||
int Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
||||
int Theme::posePreviewImageSize = 75;
|
||||
int Theme::sidebarPreferredWidth = 200;
|
||||
|
||||
QtAwesome *Theme::awesome()
|
||||
{
|
||||
|
@ -128,3 +129,22 @@ void Theme::initAwesomeToolButton(QPushButton *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;
|
||||
}
|
||||
|
|
|
@ -30,12 +30,15 @@ public:
|
|||
static std::map<QString, QString> nextSideColorNameMap;
|
||||
static std::map<QString, QColor> sideColorNameToColorMap;
|
||||
static QtAwesome *awesome();
|
||||
static QWidget *createHorizontalLineWidget();
|
||||
static QWidget *createVerticalLineWidget();
|
||||
static int toolIconFontSize;
|
||||
static int toolIconSize;
|
||||
static int previewImageRenderSize;
|
||||
static int previewImageSize;
|
||||
static int posePreviewImageSize;
|
||||
static int partPreviewImageSize;
|
||||
static int miniIconFontSize;
|
||||
static int miniIconSize;
|
||||
static int sidebarPreferredWidth;
|
||||
public:
|
||||
static void initAwesomeButton(QPushButton *button);
|
||||
static void initAwesomeLabel(QLabel *label);
|
||||
|
|
Loading…
Reference in New Issue