Introduce new UI,

Introduce part list,
Add log viewer,
Add QtAwesome icons.
master
Jeremy Hu 2018-04-07 16:44:39 +08:00
parent b99e0db0b2
commit af451bd5ab
55 changed files with 5655 additions and 1541 deletions

View File

@ -2,10 +2,9 @@ QT += core widgets opengl
CONFIG += debug CONFIG += debug
RESOURCES += resources.qrc RESOURCES += resources.qrc
INCLUDEPATH += src include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri)
SOURCES += src/mainwindow.cpp INCLUDEPATH += src
HEADERS += src/mainwindow.h
SOURCES += src/modelshaderprogram.cpp SOURCES += src/modelshaderprogram.cpp
HEADERS += src/modelshaderprogram.h HEADERS += src/modelshaderprogram.h
@ -13,27 +12,42 @@ HEADERS += src/modelshaderprogram.h
SOURCES += src/modelmeshbinder.cpp SOURCES += src/modelmeshbinder.cpp
HEADERS += src/modelmeshbinder.h HEADERS += src/modelmeshbinder.h
SOURCES += src/modelofflinerender.cpp
HEADERS += src/modelofflinerender.h
SOURCES += src/modelwidget.cpp SOURCES += src/modelwidget.cpp
HEADERS += src/modelwidget.h HEADERS += src/modelwidget.h
SOURCES += src/skeletoneditgraphicsview.cpp SOURCES += src/skeletonnodepropertywidget.cpp
HEADERS += src/skeletoneditgraphicsview.h HEADERS += src/skeletonnodepropertywidget.h
SOURCES += src/skeletoneditnodeitem.cpp SOURCES += src/skeletonedgepropertywidget.cpp
HEADERS += src/skeletoneditnodeitem.h HEADERS += src/skeletonedgepropertywidget.h
SOURCES += src/skeletoneditedgeitem.cpp SOURCES += src/skeletondocument.cpp
HEADERS += src/skeletoneditedgeitem.h HEADERS += src/skeletondocument.h
SOURCES += src/skeletontomesh.cpp SOURCES += src/skeletondocumentwindow.cpp
HEADERS += src/skeletontomesh.h HEADERS += src/skeletondocumentwindow.h
SOURCES += src/skeletongraphicswidget.cpp
HEADERS += src/skeletongraphicswidget.h
SOURCES += src/skeletonhistorylistwidget.cpp
HEADERS += src/skeletonhistorylistwidget.h
SOURCES += src/skeletonpartlistwidget.cpp
HEADERS += src/skeletonpartlistwidget.h
SOURCES += src/meshgenerator.cpp
HEADERS += src/meshgenerator.h
SOURCES += src/util.cpp
HEADERS += src/util.h
SOURCES += src/turnaroundloader.cpp SOURCES += src/turnaroundloader.cpp
HEADERS += src/turnaroundloader.h HEADERS += src/turnaroundloader.h
SOURCES += src/skeletonwidget.cpp
HEADERS += src/skeletonwidget.h
SOURCES += src/skeletonsnapshot.cpp SOURCES += src/skeletonsnapshot.cpp
HEADERS += src/skeletonsnapshot.h HEADERS += src/skeletonsnapshot.h
@ -52,6 +66,12 @@ HEADERS += src/mesh.h
SOURCES += src/unionmesh.cpp SOURCES += src/unionmesh.cpp
HEADERS += src/unionmesh.h HEADERS += src/unionmesh.h
SOURCES += src/logbrowser.cpp
HEADERS += src/logbrowser.h
SOURCES += src/logbrowserdialog.cpp
HEADERS += src/logbrowserdialog.h
SOURCES += src/main.cpp SOURCES += src/main.cpp
INCLUDEPATH += ../meshlite/include INCLUDEPATH += ../meshlite/include

24
src/logbrowser.cpp Normal file
View File

@ -0,0 +1,24 @@
#include "logbrowser.h"
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
#include <QMetaType>
#include "logbrowserdialog.h"
LogBrowser::LogBrowser(QObject *parent) :
QObject(parent)
{
qRegisterMetaType<QtMsgType>("QtMsgType");
m_browserDialog = new LogBrowserDialog;
connect(this, SIGNAL(sendMessage(QtMsgType,QString)), m_browserDialog, SLOT(outputMessage(QtMsgType,QString)), Qt::QueuedConnection);
m_browserDialog->show();
}
LogBrowser::~LogBrowser()
{
delete m_browserDialog;
}
void LogBrowser::outputMessage(QtMsgType type, const QString &msg)
{
printf("%s\n", msg.toUtf8().constData());
emit sendMessage( type, msg );
}

25
src/logbrowser.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef LOG_BROWSER_H
#define LOG_BROWSER_H
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
#include <QObject>
class LogBrowserDialog;
class LogBrowser : public QObject
{
Q_OBJECT
public:
explicit LogBrowser(QObject *parent = 0);
~LogBrowser();
public slots:
void outputMessage(QtMsgType type, const QString &msg);
signals:
void sendMessage(QtMsgType type, const QString &msg);
private:
LogBrowserDialog *m_browserDialog;
};
#endif // LOGBROWSER_H

120
src/logbrowserdialog.cpp Normal file
View File

@ -0,0 +1,120 @@
#include "logbrowserdialog.h"
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTextBrowser>
#include <QPushButton>
#include <QFileDialog>
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QTextStream>
#include <QCloseEvent>
#include <QKeyEvent>
LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
QDialog(parent)
{
QVBoxLayout *layout = new QVBoxLayout;
setLayout(layout);
m_browser = new QTextBrowser(this);
layout->addWidget(m_browser);
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->setContentsMargins(0, 0, 0, 0);
layout->addLayout(buttonLayout);
buttonLayout->addStretch(10);
m_clearButton = new QPushButton(this);
m_clearButton->setText("Clear");
buttonLayout->addWidget(m_clearButton);
connect(m_clearButton, SIGNAL(clicked()), m_browser, SLOT(clear()));
m_saveButton = new QPushButton(this);
m_saveButton->setText("Save Output");
buttonLayout->addWidget(m_saveButton);
connect(m_saveButton, SIGNAL(clicked()), this, SLOT(save()));
resize(400, 300);
}
LogBrowserDialog::~LogBrowserDialog()
{
}
void LogBrowserDialog::outputMessage(QtMsgType type, const QString &msg)
{
switch (type) {
case QtDebugMsg:
m_browser->append(tr("— DEBUG: %1").arg(msg));
break;
case QtWarningMsg:
m_browser->append(tr("— WARNING: %1").arg(msg));
break;
case QtCriticalMsg:
m_browser->append(tr("— CRITICAL: %1").arg(msg));
break;
case QtInfoMsg:
m_browser->append(tr("— INFO: %1").arg(msg));
break;
case QtFatalMsg:
m_browser->append(tr("— FATAL: %1").arg(msg));
break;
}
}
void LogBrowserDialog::save()
{
QString saveFileName = QFileDialog::getSaveFileName(this,
tr("Save Log Output"),
tr("%1/logfile.txt").arg(QDir::homePath()),
tr("Text Files (*.txt);;All Files (*)"));
if (saveFileName.isEmpty())
return;
QFile file(saveFileName);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::warning(this,
tr("Error"),
QString(tr("<nobr>File '%1'<br/>cannot be opened for writing.<br/><br/>"
"The log output could <b>not</b> be saved!</nobr>"))
.arg(saveFileName));
return;
}
QTextStream stream(&file);
stream << m_browser->toPlainText();
file.close();
}
void LogBrowserDialog::closeEvent(QCloseEvent *e)
{
QMessageBox::StandardButton answer = QMessageBox::question(this,
tr("Close Log Browser?"),
tr("Do you really want to close the log browser?"),
QMessageBox::Yes | QMessageBox::No);
if (answer == QMessageBox::Yes)
e->accept();
else
e->ignore();
}
void LogBrowserDialog::keyPressEvent(QKeyEvent *e)
{
// ignore all keyboard events
// protects against accidentally closing of the dialog
// without asking the user
e->ignore();
}

31
src/logbrowserdialog.h Normal file
View File

@ -0,0 +1,31 @@
#ifndef LOG_BROWSER_DIALOG_H
#define LOG_BROWSER_DIALOG_H
// Modified from https://wiki.qt.io/Browser_for_QDebug_output
#include <QDialog>
class QTextBrowser;
class QPushButton;
class LogBrowserDialog : public QDialog
{
Q_OBJECT
public:
LogBrowserDialog(QWidget *parent = 0);
~LogBrowserDialog();
public slots:
void outputMessage(QtMsgType type, const QString &msg);
protected slots:
void save();
protected:
virtual void keyPressEvent(QKeyEvent *e);
virtual void closeEvent(QCloseEvent *e);
QTextBrowser *m_browser;
QPushButton *m_clearButton;
QPushButton *m_saveButton;
};
#endif // DIALOG_H

View File

@ -2,8 +2,20 @@
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QStyleFactory> #include <QStyleFactory>
#include <QFontDatabase> #include <QFontDatabase>
#include "mainwindow.h" #include <QPointer>
#include "meshlite.h" #include <QDebug>
#include <QtGlobal>
#include "logbrowser.h"
#include "skeletondocumentwindow.h"
#include "theme.h"
QPointer<LogBrowser> g_logBrowser;
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if (g_logBrowser)
g_logBrowser->outputMessage(type, msg);
}
int main(int argc, char ** argv) int main(int argc, char ** argv)
{ {
@ -24,8 +36,8 @@ int main(int argc, char ** argv)
darkPalette.setColor(QPalette::ButtonText, QColor(239,239,239)); darkPalette.setColor(QPalette::ButtonText, QColor(239,239,239));
darkPalette.setColor(QPalette::BrightText, Qt::red); darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(252, 102, 33)); darkPalette.setColor(QPalette::Highlight, Theme::white);
darkPalette.setColor(QPalette::HighlightedText, Qt::black); darkPalette.setColor(QPalette::HighlightedText, Qt::white);
qApp->setPalette(darkPalette); qApp->setPalette(darkPalette);
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #fc6621; border: 1px solid white; }"); qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #fc6621; border: 1px solid white; }");
@ -36,8 +48,11 @@ int main(int argc, char ** argv)
font.setPixelSize(9); font.setPixelSize(9);
font.setBold(false); font.setBold(false);
QApplication::setFont(font); QApplication::setFont(font);
g_logBrowser = new LogBrowser;
qInstallMessageHandler(&outputMessage);
MainWindow mainWindow; SkeletonDocumentWindow mainWindow;
mainWindow.showMaximized(); mainWindow.showMaximized();
return app.exec(); return app.exec();
} }

230
src/meshgenerator.cpp Normal file
View File

@ -0,0 +1,230 @@
#include <vector>
#include <QGuiApplication>
#include "meshgenerator.h"
#include "util.h"
#include "skeletondocument.h"
#include "meshlite.h"
#include "modelofflinerender.h"
#include "unionmesh.h"
#include "theme.h"
MeshGenerator::MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread) :
m_snapshot(snapshot),
m_mesh(nullptr),
m_preview(nullptr),
m_requirePreview(false),
m_previewRender(nullptr),
m_thread(thread)
{
}
MeshGenerator::~MeshGenerator()
{
delete m_snapshot;
delete m_mesh;
delete m_preview;
for (const auto &partPreviewIt: m_partPreviewMap) {
delete partPreviewIt.second;
}
for (const auto &render: m_partPreviewRenderMap) {
delete render.second;
}
delete m_previewRender;
}
void MeshGenerator::addPreviewRequirement()
{
m_requirePreview = true;
if (nullptr == m_previewRender) {
m_previewRender = new ModelOfflineRender;
m_previewRender->setRenderThread(m_thread);
}
}
void MeshGenerator::addPartPreviewRequirement(const QString &partId)
{
qDebug() << "addPartPreviewRequirement:" << partId;
m_requirePartPreviewMap.insert(partId);
if (m_partPreviewRenderMap.find(partId) == m_partPreviewRenderMap.end()) {
ModelOfflineRender *render = new ModelOfflineRender;
render->setRenderThread(m_thread);
m_partPreviewRenderMap[partId] = render;
}
}
Mesh *MeshGenerator::takeResultMesh()
{
Mesh *resultMesh = m_mesh;
m_mesh = nullptr;
return resultMesh;
}
QImage *MeshGenerator::takePreview()
{
QImage *resultPreview = m_preview;
m_preview = nullptr;
return resultPreview;
}
QImage *MeshGenerator::takePartPreview(const QString &partId)
{
QImage *resultImage = m_partPreviewMap[partId];
m_partPreviewMap[partId] = nullptr;
return resultImage;
}
void MeshGenerator::resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile)
{
float left = -1;
float right = -1;
float top = -1;
float bottom = -1;
float zLeft = -1;
float zRight = -1;
for (const auto &nodeIt: m_snapshot->nodes) {
float radius = valueOfKeyInMapOrEmpty(nodeIt.second, "radius").toFloat();
float x = valueOfKeyInMapOrEmpty(nodeIt.second, "x").toFloat();
float y = valueOfKeyInMapOrEmpty(nodeIt.second, "y").toFloat();
float z = valueOfKeyInMapOrEmpty(nodeIt.second, "z").toFloat();
if (left < 0 || x - radius < left) {
left = x - radius;
}
if (top < 0 || y - radius < top) {
top = y - radius;
}
if (x + radius > right) {
right = x + radius;
}
if (y + radius > bottom) {
bottom = y + radius;
}
if (zLeft < 0 || z - radius < zLeft) {
zLeft = z - radius;
}
if (z + radius > zRight) {
zRight = z + radius;
}
}
*mainProfile = QRectF(left, top, right - left, bottom - top);
*sideProfile = QRectF(zLeft, top, zRight - zLeft, bottom - top);
}
void MeshGenerator::process()
{
if (nullptr == m_snapshot)
return;
void *meshliteContext = meshlite_create_context();
std::map<QString, int> partBmeshMap;
std::map<QString, int> bmeshNodeMap;
QRectF mainProfile, sideProfile;
resolveBoundingBox(&mainProfile, &sideProfile);
float longHeight = mainProfile.height();
if (mainProfile.width() > longHeight)
longHeight = mainProfile.width();
if (sideProfile.width() > longHeight)
longHeight = sideProfile.width();
float mainProfileMiddleX = mainProfile.x() + mainProfile.width() / 2;
float sideProfileMiddleX = sideProfile.x() + sideProfile.width() / 2;
float mainProfileMiddleY = mainProfile.y() + mainProfile.height() / 2;
for (const auto &partIdIt: m_snapshot->partIdList) {
int bmeshId = meshlite_bmesh_create(meshliteContext);
partBmeshMap[partIdIt] = bmeshId;
}
for (const auto &edgeIt: m_snapshot->edges) {
QString partId = valueOfKeyInMapOrEmpty(edgeIt.second, "partId");
QString fromNodeId = valueOfKeyInMapOrEmpty(edgeIt.second, "from");
QString toNodeId = valueOfKeyInMapOrEmpty(edgeIt.second, "to");
qDebug() << "Processing edge " << fromNodeId << "<=>" << toNodeId;
const auto fromIt = m_snapshot->nodes.find(fromNodeId);
const auto toIt = m_snapshot->nodes.find(toNodeId);
if (fromIt == m_snapshot->nodes.end() || toIt == m_snapshot->nodes.end())
continue;
const auto partBmeshIt = partBmeshMap.find(partId);
if (partBmeshIt == partBmeshMap.end())
continue;
int bmeshId = partBmeshIt->second;
int bmeshFromNodeId = 0;
const auto bmeshFromIt = bmeshNodeMap.find(fromNodeId);
if (bmeshFromIt == bmeshNodeMap.end()) {
float radius = valueOfKeyInMapOrEmpty(fromIt->second, "radius").toFloat() / longHeight;
float x = (valueOfKeyInMapOrEmpty(fromIt->second, "x").toFloat() - mainProfileMiddleX) / longHeight;
float y = (valueOfKeyInMapOrEmpty(fromIt->second, "y").toFloat() - mainProfileMiddleY) / longHeight;
float z = (valueOfKeyInMapOrEmpty(fromIt->second, "z").toFloat() - sideProfileMiddleX) / longHeight;
bmeshFromNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, radius);
qDebug() << "bmeshId[" << bmeshId << "] add node[" << bmeshFromNodeId << "]" << radius << x << y << z;
bmeshNodeMap[fromNodeId] = bmeshFromNodeId;
} else {
bmeshFromNodeId = bmeshFromIt->second;
qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshFromNodeId << "]";
}
int bmeshToNodeId = 0;
const auto bmeshToIt = bmeshNodeMap.find(toNodeId);
if (bmeshToIt == bmeshNodeMap.end()) {
float radius = valueOfKeyInMapOrEmpty(toIt->second, "radius").toFloat() / longHeight;
float x = (valueOfKeyInMapOrEmpty(toIt->second, "x").toFloat() - mainProfileMiddleX) / longHeight;
float y = (valueOfKeyInMapOrEmpty(toIt->second, "y").toFloat() - mainProfileMiddleY) / longHeight;
float z = (valueOfKeyInMapOrEmpty(toIt->second, "z").toFloat() - sideProfileMiddleX) / longHeight;
bmeshToNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, radius);
qDebug() << "bmeshId[" << bmeshId << "] add node[" << bmeshToNodeId << "]" << radius << x << y << z;
bmeshNodeMap[toNodeId] = bmeshToNodeId;
} else {
bmeshToNodeId = bmeshToIt->second;
qDebug() << "bmeshId[" << bmeshId << "] use existed node[" << bmeshToNodeId << "]";
}
meshlite_bmesh_add_edge(meshliteContext, bmeshId, bmeshFromNodeId, bmeshToNodeId);
}
std::map<QString, int> partMeshMap;
std::vector<int> meshIds;
for (const auto &partIdIt: m_snapshot->partIdList) {
int bmeshId = partBmeshMap[partIdIt];
int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId, 0);
qDebug() << "m_requirePartPreviewMap.find:" << partIdIt;
if (m_requirePartPreviewMap.find(partIdIt) != m_requirePartPreviewMap.end()) {
ModelOfflineRender *render = m_partPreviewRenderMap[partIdIt];
render->updateMesh(new Mesh(meshliteContext, meshId));
QImage *image = new QImage(render->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize)));
m_partPreviewMap[partIdIt] = image;
}
int triangulatedMeshId = meshlite_triangulate(meshliteContext, meshId);
meshIds.push_back(triangulatedMeshId);
}
int mergedMeshId = 0;
if (meshIds.size() > 0) {
mergedMeshId = unionMeshs(meshliteContext, meshIds);
if (mergedMeshId > 0) {
mergedMeshId = meshlite_combine_coplanar_faces(meshliteContext, mergedMeshId);
}
}
if (mergedMeshId > 0) {
if (m_requirePreview) {
m_previewRender->updateMesh(new Mesh(meshliteContext, mergedMeshId));
QImage *image = new QImage(m_previewRender->toImage(QSize(Theme::previewImageSize, Theme::previewImageSize)));
m_preview = image;
}
m_mesh = new Mesh(meshliteContext, mergedMeshId);
}
if (m_previewRender) {
m_previewRender->setRenderThread(QGuiApplication::instance()->thread());
}
for (auto &partPreviewRender: m_partPreviewRenderMap) {
partPreviewRender.second->setRenderThread(QGuiApplication::instance()->thread());
}
meshlite_destroy_context(meshliteContext);
this->moveToThread(QGuiApplication::instance()->thread());
emit finished();
}

42
src/meshgenerator.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef MESH_GENERATOR_H
#define MESH_GENERATOR_H
#include <QObject>
#include <QString>
#include <QImage>
#include <map>
#include <set>
#include <QThread>
#include "skeletonsnapshot.h"
#include "mesh.h"
#include "modelofflinerender.h"
class MeshGenerator : public QObject
{
Q_OBJECT
public:
MeshGenerator(SkeletonSnapshot *snapshot, QThread *thread);
~MeshGenerator();
void addPreviewRequirement();
void addPartPreviewRequirement(const QString &partId);
Mesh *takeResultMesh();
QImage *takePreview();
QImage *takePartPreview(const QString &partId);
signals:
void finished();
public slots:
void process();
private:
SkeletonSnapshot *m_snapshot;
Mesh *m_mesh;
QImage *m_preview;
std::map<QString, QImage *> m_partPreviewMap;
bool m_requirePreview;
std::set<QString> m_requirePartPreviewMap;
ModelOfflineRender *m_previewRender;
std::map<QString, ModelOfflineRender *> m_partPreviewRenderMap;
QThread *m_thread;
private:
void resolveBoundingBox(QRectF *mainProfile, QRectF *sideProfile);
};
#endif

107
src/modelofflinerender.cpp Normal file
View File

@ -0,0 +1,107 @@
#include <QOpenGLFramebufferObjectFormat>
#include <QThread>
#include <QDebug>
#include "modelofflinerender.h"
ModelOfflineRender::ModelOfflineRender(QScreen *targetScreen) :
QOffscreenSurface(targetScreen),
m_context(nullptr),
m_mesh(nullptr)
{
create();
m_context = new QOpenGLContext();
m_context->setFormat(format());
m_context->create();
}
ModelOfflineRender::~ModelOfflineRender()
{
delete m_context;
m_context = nullptr;
destroy();
delete m_mesh;
}
void ModelOfflineRender::updateMesh(Mesh *mesh)
{
delete m_mesh;
m_mesh = mesh;
}
void ModelOfflineRender::setRenderThread(QThread *thread)
{
m_context->moveToThread(thread);
}
QImage ModelOfflineRender::toImage(const QSize &size)
{
QImage image;
m_context->makeCurrent(this);
QOpenGLFramebufferObjectFormat format;
format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil);
QOpenGLFramebufferObject *renderFbo = new QOpenGLFramebufferObject(size, format);
renderFbo->bind();
m_context->functions()->glViewport(0, 0, size.width(), size.height());
if (nullptr != m_mesh) {
int xRot = 0;
int yRot = 0;
int zRot = 0;
QMatrix4x4 proj;
QMatrix4x4 camera;
QMatrix4x4 world;
ModelShaderProgram *program = new ModelShaderProgram;
ModelMeshBinder meshBinder;
meshBinder.initialize();
program->setUniformValue(program->lightPosLoc(), QVector3D(0, 0, 70));
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glEnable(GL_LINE_SMOOTH);
camera.setToIdentity();
camera.translate(0, 0, -2.5);
world.setToIdentity();
world.rotate(180.0f - (xRot / 16.0f), 1, 0, 0);
world.rotate(yRot / 16.0f, 0, 1, 0);
world.rotate(zRot / 16.0f, 0, 0, 1);
proj.setToIdentity();
proj.perspective(45.0f, GLfloat(size.width()) / size.height(), 0.01f, 100.0f);
program->bind();
program->setUniformValue(program->projMatrixLoc(), proj);
program->setUniformValue(program->mvMatrixLoc(), camera * world);
QMatrix3x3 normalMatrix = world.normalMatrix();
program->setUniformValue(program->normalMatrixLoc(), normalMatrix);
meshBinder.updateMesh(m_mesh);
meshBinder.paint();
meshBinder.cleanup();
program->release();
delete program;
m_mesh = nullptr;
}
m_context->functions()->glFlush();
image = renderFbo->toImage();
qDebug() << "Generated image size:" << image.size();
renderFbo->bindDefault();
delete renderFbo;
m_context->doneCurrent();
return image;
}

26
src/modelofflinerender.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef MODEL_OFFLINE_RENDER_H
#define MODEL_OFFLINE_RENDER_H
#include <QOffscreenSurface>
#include <QScreen>
#include <QOpenGLFunctions>
#include <QOpenGLContext>
#include <QImage>
#include <QThread>
#include "modelshaderprogram.h"
#include "modelmeshbinder.h"
#include "mesh.h"
class ModelOfflineRender : QOffscreenSurface
{
public:
ModelOfflineRender(QScreen *targetScreen = Q_NULLPTR);
~ModelOfflineRender();
void setRenderThread(QThread *thread);
void updateMesh(Mesh *mesh);
QImage toImage(const QSize &size);
private:
QOpenGLContext *m_context;
Mesh *m_mesh;
};
#endif

View File

@ -1,6 +1,6 @@
#include "modelwidget.h" #include "modelwidget.h"
#include "ds3file.h" #include "ds3file.h"
#include "skeletoneditgraphicsview.h" #include "skeletongraphicswidget.h"
#include <QMouseEvent> #include <QMouseEvent>
#include <QOpenGLShaderProgram> #include <QOpenGLShaderProgram>
#include <QCoreApplication> #include <QCoreApplication>
@ -16,9 +16,9 @@ ModelWidget::ModelWidget(QWidget *parent)
m_xRot(0), m_xRot(0),
m_yRot(0), m_yRot(0),
m_zRot(0), m_zRot(0),
m_program(NULL), m_program(nullptr),
m_moveStarted(false), m_moveStarted(false),
m_graphicsView(NULL) m_graphicsFunctions(NULL)
{ {
// --transparent causes the clear color to be transparent. Therefore, on systems that // --transparent causes the clear color to be transparent. Therefore, on systems that
// support it, the widget will become transparent apart from the logo. // support it, the widget will become transparent apart from the logo.
@ -34,11 +34,12 @@ ModelWidget::ModelWidget(QWidget *parent)
fmt.setSamples(4); fmt.setSamples(4);
setFormat(fmt); setFormat(fmt);
} }
setMouseTracking(true);
} }
void ModelWidget::setGraphicsView(SkeletonEditGraphicsView *view) void ModelWidget::setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions)
{ {
m_graphicsView = view; m_graphicsFunctions = graphicsFunctions;
} }
ModelWidget::~ModelWidget() ModelWidget::~ModelWidget()
@ -91,7 +92,7 @@ void ModelWidget::cleanup()
makeCurrent(); makeCurrent();
m_meshBinder.cleanup(); m_meshBinder.cleanup();
delete m_program; delete m_program;
m_program = 0; m_program = nullptr;
doneCurrent(); doneCurrent();
} }
@ -124,7 +125,7 @@ void ModelWidget::initializeGL()
// Our camera never changes in this example. // Our camera never changes in this example.
m_camera.setToIdentity(); m_camera.setToIdentity();
m_camera.translate(0, 0, -2.5); m_camera.translate(0, 0, -3.5);
// Light position is fixed. // Light position is fixed.
m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70)); m_program->setUniformValue(m_program->lightPosLoc(), QVector3D(0, 0, 70));
@ -164,10 +165,9 @@ void ModelWidget::resizeGL(int w, int h)
void ModelWidget::mousePressEvent(QMouseEvent *event) void ModelWidget::mousePressEvent(QMouseEvent *event)
{ {
if (!m_moveStarted && m_graphicsView && m_graphicsView->mousePress(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mousePress(event))
return; return;
m_lastPos = event->pos(); m_lastPos = event->pos();
setMouseTracking(true);
if (event->button() == Qt::LeftButton) { if (event->button() == Qt::LeftButton) {
if (!m_moveStarted) { if (!m_moveStarted) {
m_moveStartPos = mapToParent(event->pos()); m_moveStartPos = mapToParent(event->pos());
@ -179,7 +179,7 @@ void ModelWidget::mousePressEvent(QMouseEvent *event)
void ModelWidget::mouseReleaseEvent(QMouseEvent *event) void ModelWidget::mouseReleaseEvent(QMouseEvent *event)
{ {
if (!m_moveStarted && m_graphicsView && m_graphicsView->mouseRelease(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mouseRelease(event))
return; return;
if (m_moveStarted) { if (m_moveStarted) {
m_moveStarted = false; m_moveStarted = false;
@ -188,7 +188,7 @@ void ModelWidget::mouseReleaseEvent(QMouseEvent *event)
void ModelWidget::mouseMoveEvent(QMouseEvent *event) void ModelWidget::mouseMoveEvent(QMouseEvent *event)
{ {
if (!m_moveStarted && m_graphicsView && m_graphicsView->mouseMove(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->mouseMove(event))
return; return;
int dx = event->x() - m_lastPos.x(); int dx = event->x() - m_lastPos.x();
@ -210,7 +210,7 @@ void ModelWidget::mouseMoveEvent(QMouseEvent *event)
void ModelWidget::wheelEvent(QWheelEvent *event) void ModelWidget::wheelEvent(QWheelEvent *event)
{ {
if (!m_moveStarted && m_graphicsView && m_graphicsView->wheel(event, m_graphicsView->mapToScene(m_graphicsView->mapFromGlobal(event->globalPos())))) if (!m_moveStarted && m_graphicsFunctions && m_graphicsFunctions->wheel(event))
return; return;
if (m_moveStarted) if (m_moveStarted)
return; return;

View File

@ -11,7 +11,7 @@
#include "modelshaderprogram.h" #include "modelshaderprogram.h"
#include "modelmeshbinder.h" #include "modelmeshbinder.h"
class SkeletonEditGraphicsView; class SkeletonGraphicsFunctions;
class ModelWidget : public QOpenGLWidget, protected QOpenGLFunctions class ModelWidget : public QOpenGLWidget, protected QOpenGLFunctions
{ {
@ -26,7 +26,7 @@ public:
void updateMesh(Mesh *mesh); void updateMesh(Mesh *mesh);
void exportMeshAsObj(const QString &filename); void exportMeshAsObj(const QString &filename);
void setGraphicsView(SkeletonEditGraphicsView *view); void setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions);
public slots: public slots:
void setXRotation(int angle); void setXRotation(int angle);
@ -61,7 +61,7 @@ private:
bool m_moveStarted; bool m_moveStarted;
QPoint m_moveStartPos; QPoint m_moveStartPos;
QRect m_moveStartGeometry; QRect m_moveStartGeometry;
SkeletonEditGraphicsView *m_graphicsView; SkeletonGraphicsFunctions *m_graphicsFunctions;
}; };
#endif #endif

706
src/skeletondocument.cpp Normal file
View File

@ -0,0 +1,706 @@
#include <QFileDialog>
#include <QDebug>
#include <QThread>
#include <QGuiApplication>
#include "skeletondocument.h"
#include "util.h"
SkeletonDocument::SkeletonDocument() :
m_resultMeshIsObsolete(false),
m_resultMesh(nullptr),
m_meshGenerator(nullptr)
{
}
SkeletonDocument::~SkeletonDocument()
{
delete m_resultMesh;
}
void SkeletonDocument::uiReady()
{
emit editModeChanged();
}
void SkeletonDocument::removePart(QUuid partId)
{
}
void SkeletonDocument::showPart(QUuid partId)
{
}
void SkeletonDocument::hidePart(QUuid partId)
{
}
void SkeletonDocument::removeEdge(QUuid edgeId)
{
const SkeletonEdge *edge = findEdge(edgeId);
if (nullptr == edge) {
qDebug() << "Find edge failed:" << edgeId;
return;
}
const SkeletonPart *oldPart = findPart(edge->partId);
if (nullptr == oldPart) {
qDebug() << "Find part failed:" << edge->partId;
return;
}
QString nextPartName = oldPart->name;
QUuid oldPartId = oldPart->id;
std::vector<std::vector<QUuid>> groups;
splitPartByEdge(&groups, edgeId);
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
SkeletonPart part;
part.name = nextPartName;
for (auto nodeIdIt = (*groupIt).begin(); nodeIdIt != (*groupIt).end(); nodeIdIt++) {
auto nodeIt = nodeMap.find(*nodeIdIt);
if (nodeIt == nodeMap.end()) {
qDebug() << "Find node failed:" << *nodeIdIt;
continue;
}
nodeIt->second.partId = part.id;
part.nodeIds.push_back(nodeIt->first);
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
edgeIt->second.partId = part.id;
}
}
partMap[part.id] = part;
partIds.push_back(part.id);
emit partAdded(part.id);
}
for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) {
auto nodeIt = nodeMap.find(*nodeIdIt);
if (nodeIt == nodeMap.end()) {
qDebug() << "Find node failed:" << *nodeIdIt;
continue;
}
nodeIt->second.edgeIds.erase(std::remove(nodeIt->second.edgeIds.begin(), nodeIt->second.edgeIds.end(), edgeId), nodeIt->second.edgeIds.end());
emit nodeOriginChanged(nodeIt->first);
}
edgeMap.erase(edgeId);
emit edgeRemoved(edgeId);
partIds.erase(std::remove(partIds.begin(), partIds.end(), oldPartId), partIds.end());
partMap.erase(oldPartId);
emit partRemoved(oldPartId);
emit partListChanged();
emit skeletonChanged();
}
void SkeletonDocument::removeNode(QUuid nodeId)
{
const SkeletonNode *node = findNode(nodeId);
if (nullptr == node) {
qDebug() << "Find node failed:" << nodeId;
return;
}
const SkeletonPart *oldPart = findPart(node->partId);
if (nullptr == oldPart) {
qDebug() << "Find part failed:" << node->partId;
return;
}
QString nextPartName = oldPart->name;
QUuid oldPartId = oldPart->id;
std::vector<std::vector<QUuid>> groups;
splitPartByNode(&groups, nodeId);
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
SkeletonPart part;
part.name = nextPartName;
for (auto nodeIdIt = (*groupIt).begin(); nodeIdIt != (*groupIt).end(); nodeIdIt++) {
auto nodeIt = nodeMap.find(*nodeIdIt);
if (nodeIt == nodeMap.end()) {
qDebug() << "Find node failed:" << *nodeIdIt;
continue;
}
nodeIt->second.partId = part.id;
part.nodeIds.push_back(nodeIt->first);
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
edgeIt->second.partId = part.id;
}
}
partMap[part.id] = part;
partIds.push_back(part.id);
emit partAdded(part.id);
}
for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
QUuid neighborId = edgeIt->second.neighborOf(nodeId);
auto nodeIt = nodeMap.find(neighborId);
if (nodeIt == nodeMap.end()) {
qDebug() << "Find node failed:" << neighborId;
continue;
}
nodeIt->second.edgeIds.erase(std::remove(nodeIt->second.edgeIds.begin(), nodeIt->second.edgeIds.end(), *edgeIdIt), nodeIt->second.edgeIds.end());
edgeMap.erase(*edgeIdIt);
emit edgeRemoved(*edgeIdIt);
}
nodeMap.erase(nodeId);
emit nodeRemoved(nodeId);
partIds.erase(std::remove(partIds.begin(), partIds.end(), oldPartId), partIds.end());
partMap.erase(oldPartId);
emit partRemoved(oldPartId);
emit partListChanged();
emit skeletonChanged();
}
void SkeletonDocument::addNode(float x, float y, float z, float radius, QUuid fromNodeId)
{
QUuid partId;
const SkeletonNode *fromNode = nullptr;
bool newPartAdded = false;
if (fromNodeId.isNull()) {
SkeletonPart part;
partMap[part.id] = part;
partIds.push_back(part.id);
partId = part.id;
emit partAdded(partId);
newPartAdded = true;
} else {
fromNode = findNode(fromNodeId);
if (nullptr == fromNode) {
qDebug() << "Find node failed:" << fromNodeId;
return;
}
partId = fromNode->partId;
}
SkeletonNode node;
node.partId = partId;
node.radius = radius;
node.x = x;
node.y = y;
node.z = z;
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) {
SkeletonEdge edge;
edge.partId = partId;
edge.nodeIds.push_back(fromNode->id);
edge.nodeIds.push_back(node.id);
edgeMap[edge.id] = edge;
nodeMap[node.id].edgeIds.push_back(edge.id);
nodeMap[fromNode->id].edgeIds.push_back(edge.id);
emit edgeAdded(edge.id);
}
emit partChanged(partId);
if (newPartAdded)
emit partListChanged();
emit skeletonChanged();
}
const SkeletonEdge *SkeletonDocument::findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const
{
const SkeletonNode *firstNode = nullptr;
firstNode = findNode(firstNodeId);
if (nullptr == firstNode) {
qDebug() << "Find node failed:" << firstNodeId;
return nullptr;
}
for (auto edgeIdIt = firstNode->edgeIds.begin(); edgeIdIt != firstNode->edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
if (std::find(edgeIt->second.nodeIds.begin(), edgeIt->second.nodeIds.end(), secondNodeId) != edgeIt->second.nodeIds.end())
return &edgeIt->second;
}
return nullptr;
}
void SkeletonDocument::addEdge(QUuid fromNodeId, QUuid toNodeId)
{
const SkeletonNode *fromNode = nullptr;
const SkeletonNode *toNode = nullptr;
bool toPartRemoved = false;
fromNode = findNode(fromNodeId);
if (nullptr == fromNode) {
qDebug() << "Find node failed:" << fromNodeId;
return;
}
toNode = findNode(toNodeId);
if (nullptr == toNode) {
qDebug() << "Find node failed:" << toNodeId;
return;
}
QUuid toPartId = toNode->partId;
auto fromPart = partMap.find(fromNode->partId);
if (fromPart == partMap.end()) {
qDebug() << "Find part failed:" << fromNode->partId;
return;
}
if (fromNode->partId != toNode->partId) {
toPartRemoved = true;
std::vector<QUuid> toGroup;
std::set<QUuid> visitMap;
joinNodeAndNeiborsToGroup(&toGroup, toNodeId, &visitMap);
for (auto nodeIdIt = toGroup.begin(); nodeIdIt != toGroup.end(); nodeIdIt++) {
auto nodeIt = nodeMap.find(*nodeIdIt);
if (nodeIt == nodeMap.end()) {
qDebug() << "Find node failed:" << *nodeIdIt;
continue;
}
nodeIt->second.partId = fromNode->partId;
fromPart->second.nodeIds.push_back(nodeIt->first);
for (auto edgeIdIt = nodeIt->second.edgeIds.begin(); edgeIdIt != nodeIt->second.edgeIds.end(); edgeIdIt++) {
auto edgeIt = edgeMap.find(*edgeIdIt);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << *edgeIdIt;
continue;
}
edgeIt->second.partId = fromNode->partId;
}
}
}
SkeletonEdge edge;
edge.partId = fromNode->partId;
edge.nodeIds.push_back(fromNode->id);
edge.nodeIds.push_back(toNodeId);
edgeMap[edge.id] = edge;
nodeMap[toNodeId].edgeIds.push_back(edge.id);
nodeMap[fromNode->id].edgeIds.push_back(edge.id);
emit edgeAdded(edge.id);
if (toPartRemoved) {
partIds.erase(std::remove(partIds.begin(), partIds.end(), toPartId), partIds.end());
partMap.erase(toPartId);
emit partRemoved(toPartId);
emit partListChanged();
}
emit skeletonChanged();
}
const SkeletonNode *SkeletonDocument::findNode(QUuid nodeId) const
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end())
return nullptr;
return &it->second;
}
const SkeletonEdge *SkeletonDocument::findEdge(QUuid edgeId) const
{
auto it = edgeMap.find(edgeId);
if (it == edgeMap.end())
return nullptr;
return &it->second;
}
const SkeletonPart *SkeletonDocument::findPart(QUuid partId) const
{
auto it = partMap.find(partId);
if (it == partMap.end())
return nullptr;
return &it->second;
}
void SkeletonDocument::scaleNodeByAddRadius(QUuid nodeId, float amount)
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
it->second.radius += amount;
emit nodeRadiusChanged(nodeId);
emit skeletonChanged();
}
void SkeletonDocument::moveNodeBy(QUuid nodeId, float x, float y, float z)
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
it->second.x += x;
it->second.y += y;
it->second.z += z;
emit nodeOriginChanged(nodeId);
emit skeletonChanged();
}
void SkeletonDocument::setNodeOrigin(QUuid nodeId, float x, float y, float z)
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
it->second.x = x;
it->second.y = y;
it->second.z = z;
emit nodeOriginChanged(nodeId);
emit skeletonChanged();
}
void SkeletonDocument::setNodeRadius(QUuid nodeId, float radius)
{
auto it = nodeMap.find(nodeId);
if (it == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
it->second.radius = radius;
emit nodeRadiusChanged(nodeId);
emit skeletonChanged();
}
void SkeletonDocument::updateTurnaround(const QImage &image)
{
turnaround = image;
emit turnaroundChanged();
}
void SkeletonDocument::setEditMode(SkeletonDocumentEditMode mode)
{
if (editMode == mode)
return;
editMode = mode;
emit editModeChanged();
}
void SkeletonDocument::joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId)
{
if (nodeId.isNull() || visitMap->find(nodeId) != visitMap->end())
return;
const SkeletonNode *node = findNode(nodeId);
if (nullptr == node) {
qDebug() << "Find node failed:" << nodeId;
return;
}
visitMap->insert(nodeId);
group->push_back(nodeId);
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
if (noUseEdgeId == *edgeIt)
continue;
const SkeletonEdge *edge = findEdge(*edgeIt);
if (nullptr == edge) {
qDebug() << "Find edge failed:" << *edgeIt;
continue;
}
for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) {
joinNodeAndNeiborsToGroup(group, *nodeIt, visitMap, noUseEdgeId);
}
}
}
void SkeletonDocument::splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId)
{
const SkeletonNode *node = findNode(nodeId);
std::set<QUuid> visitMap;
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
std::vector<QUuid> group;
const SkeletonEdge *edge = findEdge(*edgeIt);
if (nullptr == edge) {
qDebug() << "Find edge failed:" << *edgeIt;
continue;
}
joinNodeAndNeiborsToGroup(&group, edge->neighborOf(nodeId), &visitMap, *edgeIt);
if (!group.empty())
groups->push_back(group);
}
}
void SkeletonDocument::splitPartByEdge(std::vector<std::vector<QUuid>> *groups, QUuid edgeId)
{
const SkeletonEdge *edge = findEdge(edgeId);
if (nullptr == edge) {
qDebug() << "Find edge failed:" << edgeId;
return;
}
std::set<QUuid> visitMap;
for (auto nodeIt = edge->nodeIds.begin(); nodeIt != edge->nodeIds.end(); nodeIt++) {
std::vector<QUuid> group;
joinNodeAndNeiborsToGroup(&group, *nodeIt, &visitMap, edgeId);
if (!group.empty())
groups->push_back(group);
}
}
void SkeletonDocument::setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode)
{
auto edgeIt = edgeMap.find(edgeId);
if (edgeIt == edgeMap.end()) {
qDebug() << "Find edge failed:" << edgeId;
return;
}
edgeIt->second.branchMode = mode;
}
void SkeletonDocument::setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode)
{
auto nodeIt = nodeMap.find(nodeId);
if (nodeIt == nodeMap.end()) {
qDebug() << "Find node failed:" << nodeId;
return;
}
nodeIt->second.rootMarkMode = mode;
}
void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot)
{
for (const auto &partIt : partMap) {
std::map<QString, QString> part;
part["id"] = partIt.second.id.toString();
part["visible"] = partIt.second.visible ? "true" : "false";
if (!partIt.second.name.isEmpty())
part["name"] = partIt.second.name;
snapshot->parts[part["id"]] = part;
}
for (const auto &nodeIt: nodeMap) {
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["rootMarkMode"] = SkeletonNodeRootMarkModeToString(nodeIt.second.rootMarkMode);
node["partId"] = nodeIt.second.partId.toString();
if (!nodeIt.second.name.isEmpty())
node["name"] = nodeIt.second.name;
snapshot->nodes[node["id"]] = node;
qDebug() << "Export node to snapshot " << node["id"] << node["x"] << node["y"] << node["z"];
}
for (const auto &edgeIt: edgeMap) {
if (edgeIt.second.nodeIds.size() != 2)
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["branchMode"] = SkeletonEdgeBranchModeToString(edgeIt.second.branchMode);
edge["partId"] = edgeIt.second.partId.toString();
if (!edgeIt.second.name.isEmpty())
edge["name"] = edgeIt.second.name;
snapshot->edges[edge["id"]] = edge;
qDebug() << "Export edge to snapshot " << edge["from"] << "<=>" << edge["to"];
}
for (const auto &partIdIt: partIds) {
snapshot->partIdList.push_back(partIdIt.toString());
}
}
void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
{
for (const auto &nodeIt: nodeMap) {
emit nodeRemoved(nodeIt.first);
}
for (const auto &edgeIt: edgeMap) {
emit edgeRemoved(edgeIt.first);
}
for (const auto &partIt : partMap) {
emit partRemoved(partIt.first);
}
nodeMap.clear();
edgeMap.clear();
partMap.clear();
for (const auto &nodeKv : snapshot.nodes) {
SkeletonNode node(QUuid(nodeKv.first));
node.name = valueOfKeyInMapOrEmpty(nodeKv.second, "name");
node.radius = valueOfKeyInMapOrEmpty(nodeKv.second, "radius").toFloat();
node.x = valueOfKeyInMapOrEmpty(nodeKv.second, "x").toFloat();
node.y = valueOfKeyInMapOrEmpty(nodeKv.second, "y").toFloat();
node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat();
node.partId = QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"));
node.rootMarkMode = SkeletonNodeRootMarkModeFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "rootMarkMode"));
nodeMap[node.id] = node;
}
for (const auto &edgeKv : snapshot.edges) {
SkeletonEdge edge(QUuid(edgeKv.first));
edge.name = valueOfKeyInMapOrEmpty(edgeKv.second, "name");
edge.partId = QUuid(valueOfKeyInMapOrEmpty(edgeKv.second, "partId"));
edge.branchMode = SkeletonEdgeBranchModeFromString(valueOfKeyInMapOrEmpty(edgeKv.second, "branchMode"));
QString fromNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "from");
if (!fromNodeId.isEmpty())
edge.nodeIds.push_back(QUuid(fromNodeId));
QString toNodeId = valueOfKeyInMapOrEmpty(edgeKv.second, "to");
if (!toNodeId.isEmpty())
edge.nodeIds.push_back(QUuid(toNodeId));
edgeMap[edge.id] = edge;
}
for (const auto &partKv : snapshot.parts) {
SkeletonPart part(QUuid(partKv.first));
part.name = valueOfKeyInMapOrEmpty(partKv.second, "name");
part.visible = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "visible"));
partMap[part.id] = part;
}
for (const auto &partIdIt: snapshot.partIdList) {
partIds.push_back(QUuid(partIdIt));
}
for (const auto &nodeIt: nodeMap) {
emit nodeAdded(nodeIt.first);
}
for (const auto &edgeIt: edgeMap) {
emit edgeAdded(edgeIt.first);
}
for (const auto &partIt : partMap) {
emit partAdded(partIt.first);
}
emit partListChanged();
}
const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode)
{
switch (mode) {
case SkeletonNodeRootMarkMode::Auto:
return "auto";
case SkeletonNodeRootMarkMode::MarkAsRoot:
return "markAsRoot";
case SkeletonNodeRootMarkMode::MarkAsNotRoot:
return "markAsNotRoot";
default:
return "";
}
}
SkeletonNodeRootMarkMode SkeletonNodeRootMarkModeFromString(const QString &mode)
{
if (mode == "auto")
return SkeletonNodeRootMarkMode::Auto;
if (mode == "markAsRoot")
return SkeletonNodeRootMarkMode::MarkAsRoot;
if (mode == "markAsNotRoot")
return SkeletonNodeRootMarkMode::MarkAsNotRoot;
return SkeletonNodeRootMarkMode::Auto;
}
const char *SkeletonEdgeBranchModeToString(SkeletonEdgeBranchMode mode)
{
switch (mode) {
case SkeletonEdgeBranchMode::Auto:
return "auto";
case SkeletonEdgeBranchMode::NoTrivial:
return "noTrivial";
case SkeletonEdgeBranchMode::Trivial:
return "trivial";
default:
return "";
}
}
SkeletonEdgeBranchMode SkeletonEdgeBranchModeFromString(const QString &mode)
{
if (mode == "auto")
return SkeletonEdgeBranchMode::Auto;
if (mode == "noTrivial")
return SkeletonEdgeBranchMode::NoTrivial;
if (mode == "trivial")
return SkeletonEdgeBranchMode::Trivial;
return SkeletonEdgeBranchMode::Auto;
}
Mesh *SkeletonDocument::takeResultMesh()
{
Mesh *resultMesh = m_resultMesh;
m_resultMesh = nullptr;
return resultMesh;
}
void SkeletonDocument::meshReady()
{
Mesh *resultMesh = m_meshGenerator->takeResultMesh();
QImage *resultPreview = m_meshGenerator->takePreview();
if (resultPreview) {
preview = *resultPreview;
delete resultPreview;
}
for (auto &part: partMap) {
QImage *resultPartPreview = m_meshGenerator->takePartPreview(part.first.toString());
if (resultPartPreview) {
part.second.preview = *resultPartPreview;
emit partPreviewChanged(part.first);
delete resultPartPreview;
}
}
delete m_resultMesh;
m_resultMesh = resultMesh;
if (nullptr == m_resultMesh) {
qDebug() << "Result mesh is null";
}
delete m_meshGenerator;
m_meshGenerator = nullptr;
qDebug() << "Mesh generation done";
emit resultMeshChanged();
if (m_resultMeshIsObsolete) {
generateMesh();
}
}
void SkeletonDocument::generateMesh()
{
if (nullptr != m_meshGenerator) {
m_resultMeshIsObsolete = true;
return;
}
qDebug() << "Mesh generating..";
m_resultMeshIsObsolete = false;
QThread *thread = new QThread;
SkeletonSnapshot *snapshot = new SkeletonSnapshot;
toSnapshot(snapshot);
m_meshGenerator = new MeshGenerator(snapshot, thread);
m_meshGenerator->moveToThread(thread);
m_meshGenerator->addPreviewRequirement();
for (auto &part: partMap) {
//if (part.second.previewIsObsolete) {
// part.second.previewIsObsolete = false;
m_meshGenerator->addPartPreviewRequirement(part.first.toString());
//}
}
connect(thread, &QThread::started, m_meshGenerator, &MeshGenerator::process);
connect(m_meshGenerator, &MeshGenerator::finished, this, &SkeletonDocument::meshReady);
connect(m_meshGenerator, &MeshGenerator::finished, thread, &QThread::quit);
connect(thread, &QThread::finished, thread, &QThread::deleteLater);
thread->start();
}

184
src/skeletondocument.h Normal file
View File

@ -0,0 +1,184 @@
#ifndef SKELETON_DOCUMENT_H
#define SKELETON_DOCUMENT_H
#include <QObject>
#include <QUuid>
#include <vector>
#include <map>
#include <set>
#include <QImage>
#include "skeletonsnapshot.h"
#include "mesh.h"
#include "meshgenerator.h"
enum class SkeletonNodeRootMarkMode
{
Auto = 0,
MarkAsRoot,
MarkAsNotRoot
};
const char *SkeletonNodeRootMarkModeToString(SkeletonNodeRootMarkMode mode);
SkeletonNodeRootMarkMode SkeletonNodeRootMarkModeFromString(const QString &mode);
class SkeletonNode
{
public:
SkeletonNode(const QUuid &withId=QUuid()) :
x(0),
y(0),
z(0),
radius(0),
rootMarkMode(SkeletonNodeRootMarkMode::Auto)
{
id = withId.isNull() ? QUuid::createUuid() : withId;
}
QUuid id;
QUuid partId;
QString name;
float x;
float y;
float z;
float radius;
SkeletonNodeRootMarkMode rootMarkMode;
std::vector<QUuid> edgeIds;
};
enum class SkeletonEdgeBranchMode
{
Auto = 0,
Trivial,
NoTrivial
};
const char *SkeletonEdgeBranchModeToString(SkeletonEdgeBranchMode mode);
SkeletonEdgeBranchMode SkeletonEdgeBranchModeFromString(const QString &mode);
class SkeletonEdge
{
public:
SkeletonEdge(const QUuid &withId=QUuid()) :
branchMode(SkeletonEdgeBranchMode::Auto)
{
id = withId.isNull() ? QUuid::createUuid() : withId;
}
QUuid id;
QUuid partId;
QString name;
SkeletonEdgeBranchMode branchMode;
std::vector<QUuid> nodeIds;
QUuid neighborOf(QUuid nodeId) const
{
if (nodeIds.size() != 2)
return QUuid();
return nodeIds[0] == nodeId ? nodeIds[1] : nodeIds[0];
}
};
class SkeletonPart
{
public:
QUuid id;
QString name;
bool visible;
QImage preview;
std::vector<QUuid> nodeIds;
bool previewIsObsolete;
SkeletonPart(const QUuid &withId=QUuid()) :
visible(true),
previewIsObsolete(true)
{
id = withId.isNull() ? QUuid::createUuid() : withId;
}
};
class SkeletonHistoryItem
{
public:
SkeletonSnapshot snapshot;
QImage preview;
};
enum class SkeletonDocumentEditMode
{
Add = 0,
Select,
Drag,
ZoomIn,
ZoomOut
};
enum class SkeletonProfile
{
Unknown = 0,
Main,
Side
};
class SkeletonDocument : public QObject
{
Q_OBJECT
signals:
void partAdded(QUuid partId);
void nodeAdded(QUuid nodeId);
void edgeAdded(QUuid edgeId);
void partRemoved(QUuid partId);
void partListChanged();
void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId);
void nodeOriginChanged(QUuid nodeId);
void edgeChanged(QUuid edgeId);
void partChanged(QUuid partId);
void partPreviewChanged(QUuid partId);
void resultMeshChanged();
void turnaroundChanged();
void editModeChanged();
void skeletonChanged();
public:
SkeletonDocument();
~SkeletonDocument();
std::map<QUuid, SkeletonPart> partMap;
std::map<QUuid, SkeletonNode> nodeMap;
std::map<QUuid, SkeletonEdge> edgeMap;
std::vector<SkeletonHistoryItem> historyItems;
std::vector<QUuid> partIds;
QImage turnaround;
SkeletonDocumentEditMode editMode;
void toSnapshot(SkeletonSnapshot *snapshot);
void fromSnapshot(const SkeletonSnapshot &snapshot);
const SkeletonNode *findNode(QUuid nodeId) const;
const SkeletonEdge *findEdge(QUuid edgeId) const;
const SkeletonPart *findPart(QUuid partId) const;
const SkeletonEdge *findEdgeByNodes(QUuid firstNodeId, QUuid secondNodeId) const;
Mesh *takeResultMesh();
QImage preview;
void updateTurnaround(const QImage &image);
public slots:
void removeNode(QUuid nodeId);
void removeEdge(QUuid edgeId);
void removePart(QUuid partId);
void showPart(QUuid partId);
void hidePart(QUuid partId);
void addNode(float x, float y, float z, float radius, QUuid fromNodeId);
void scaleNodeByAddRadius(QUuid nodeId, float amount);
void moveNodeBy(QUuid nodeId, float x, float y, float z);
void setNodeOrigin(QUuid nodeId, float x, float y, float z);
void setNodeRadius(QUuid nodeId, float radius);
void addEdge(QUuid fromNodeId, QUuid toNodeId);
void setEditMode(SkeletonDocumentEditMode mode);
void setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode);
void setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode);
void uiReady();
void generateMesh();
void meshReady();
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());
void splitPartByEdge(std::vector<std::vector<QUuid>> *groups, QUuid edgeId);
private:
bool m_resultMeshIsObsolete;
MeshGenerator *m_meshGenerator;
Mesh *m_resultMesh;
};
#endif

View File

@ -0,0 +1,256 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QGridLayout>
#include <QToolBar>
#include <QPushButton>
#include <QFileDialog>
#include <QTabWidget>
#include "skeletondocumentwindow.h"
#include "skeletongraphicswidget.h"
#include "skeletonpartlistwidget.h"
#include "skeletonedgepropertywidget.h"
#include "skeletonnodepropertywidget.h"
#include "theme.h"
SkeletonDocumentWindow::SkeletonDocumentWindow() :
m_document(nullptr),
m_firstShow(true)
{
m_document = new SkeletonDocument;
QVBoxLayout *toolButtonLayout = new QVBoxLayout;
toolButtonLayout->setSpacing(0);
toolButtonLayout->setContentsMargins(5, 10, 4, 0);
QPushButton *undoButton = new QPushButton(QChar(fa::undo));
undoButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
undoButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *addButton = new QPushButton(QChar(fa::plus));
addButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
addButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *selectButton = new QPushButton(QChar(fa::mousepointer));
selectButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
selectButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *dragButton = new QPushButton(QChar(fa::handrocko));
dragButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
dragButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *zoomInButton = new QPushButton(QChar(fa::searchplus));
zoomInButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
zoomInButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *zoomOutButton = new QPushButton(QChar(fa::searchminus));
zoomOutButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
zoomOutButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *changeTurnaroundButton = new QPushButton(QChar(fa::image));
changeTurnaroundButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
changeTurnaroundButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
QPushButton *resetButton = new QPushButton(QChar(fa::trash));
resetButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
resetButton->setFixedSize(Theme::toolIconSize, Theme::toolIconSize);
toolButtonLayout->addWidget(undoButton);
toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(addButton);
toolButtonLayout->addWidget(selectButton);
toolButtonLayout->addWidget(dragButton);
toolButtonLayout->addWidget(zoomInButton);
toolButtonLayout->addWidget(zoomOutButton);
toolButtonLayout->addSpacing(10);
toolButtonLayout->addWidget(changeTurnaroundButton);
toolButtonLayout->addSpacing(30);
toolButtonLayout->addWidget(resetButton);
QLabel *dust3dJezzasoftLabel = new QLabel;
QImage dust3dJezzasoftImage;
dust3dJezzasoftImage.load(":/resources/dust3d_jezzasoft.png");
dust3dJezzasoftLabel->setPixmap(QPixmap::fromImage(dust3dJezzasoftImage));
QHBoxLayout *logoLayout = new QHBoxLayout;
logoLayout->addWidget(dust3dJezzasoftLabel);
logoLayout->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *mainLeftLayout = new QVBoxLayout;
mainLeftLayout->setSpacing(0);
mainLeftLayout->setContentsMargins(0, 0, 0, 0);
mainLeftLayout->addLayout(toolButtonLayout);
mainLeftLayout->addStretch();
mainLeftLayout->addLayout(logoLayout);
mainLeftLayout->addSpacing(10);
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document);
SkeletonGraphicsContainerWidget *containerWidget = new SkeletonGraphicsContainerWidget;
containerWidget->setGraphicsWidget(graphicsWidget);
QGridLayout *containerLayout = new QGridLayout;
containerLayout->setSpacing(0);
containerLayout->setContentsMargins(1, 0, 0, 0);
containerLayout->addWidget(graphicsWidget);
containerWidget->setLayout(containerLayout);
containerWidget->setMinimumSize(400, 400);
m_modelWidget = new ModelWidget(containerWidget);
m_modelWidget->setMinimumSize(128, 128);
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelWidget->move(10, 10);
m_modelWidget->setGraphicsFunctions(graphicsWidget);
SkeletonPartListWidget *partListWidget = new SkeletonPartListWidget(m_document);
QTabWidget *firstTabWidget = new QTabWidget;
firstTabWidget->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 3 / 4));
firstTabWidget->setMaximumWidth(200);
firstTabWidget->addTab(partListWidget, QChar(fa::puzzlepiece));
firstTabWidget->addTab(new QWidget, QChar(fa::history));
firstTabWidget->addTab(new QWidget, QChar(fa::wrench));
SkeletonEdgePropertyWidget *edgePropertyWidget = new SkeletonEdgePropertyWidget(m_document);
SkeletonNodePropertyWidget *nodePropertyWidget = new SkeletonNodePropertyWidget(m_document);
QVBoxLayout *propertyLayout = new QVBoxLayout;
propertyLayout->addWidget(edgePropertyWidget);
propertyLayout->addWidget(nodePropertyWidget);
QWidget *propertyWidget = new QWidget;
propertyWidget->setLayout(propertyLayout);
QTabWidget *secondTabWidget = new QTabWidget;
secondTabWidget->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 3 / 4));
secondTabWidget->setMaximumWidth(200);
secondTabWidget->setMaximumHeight(90);
secondTabWidget->addTab(propertyWidget, QChar(fa::adjust));
QVBoxLayout *mainRightLayout = new QVBoxLayout;
mainRightLayout->setSpacing(0);
mainRightLayout->setContentsMargins(5, 5, 5, 5);
mainRightLayout->addWidget(firstTabWidget);
mainRightLayout->addWidget(secondTabWidget);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->setSpacing(0);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->addLayout(mainLeftLayout);
mainLayout->addWidget(containerWidget);
mainLayout->addLayout(mainRightLayout);
QWidget *centralWidget = new QWidget;
centralWidget->setLayout(mainLayout);
setCentralWidget(centralWidget);
setWindowTitle(tr("Dust3D"));
connect(containerWidget, &SkeletonGraphicsContainerWidget::containerSizeChanged,
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
connect(m_document, &SkeletonDocument::turnaroundChanged,
graphicsWidget, &SkeletonGraphicsWidget::turnaroundChanged);
connect(changeTurnaroundButton, &QPushButton::clicked, this, &SkeletonDocumentWindow::changeTurnaround);
connect(addButton, &QPushButton::clicked, [=]() {
m_document->setEditMode(SkeletonDocumentEditMode::Add);
});
connect(selectButton, &QPushButton::clicked, [=]() {
m_document->setEditMode(SkeletonDocumentEditMode::Select);
});
connect(dragButton, &QPushButton::clicked, [=]() {
m_document->setEditMode(SkeletonDocumentEditMode::Drag);
});
connect(zoomInButton, &QPushButton::clicked, [=]() {
m_document->setEditMode(SkeletonDocumentEditMode::ZoomIn);
});
connect(zoomOutButton, &QPushButton::clicked, [=]() {
m_document->setEditMode(SkeletonDocumentEditMode::ZoomOut);
});
connect(m_document, &SkeletonDocument::editModeChanged, graphicsWidget, &SkeletonGraphicsWidget::editModeChanged);
connect(graphicsWidget, &SkeletonGraphicsWidget::addNode, m_document, &SkeletonDocument::addNode);
connect(graphicsWidget, &SkeletonGraphicsWidget::scaleNodeByAddRadius, m_document, &SkeletonDocument::scaleNodeByAddRadius);
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &SkeletonDocument::moveNodeBy);
connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &SkeletonDocument::removeNode);
connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &SkeletonDocument::setEditMode);
connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &SkeletonDocument::removeEdge);
connect(graphicsWidget, &SkeletonGraphicsWidget::addEdge, m_document, &SkeletonDocument::addEdge);
connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
connect(m_document, &SkeletonDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
connect(m_document, &SkeletonDocument::edgeRemoved, graphicsWidget, &SkeletonGraphicsWidget::edgeRemoved);
connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged);
connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
connect(m_document, &SkeletonDocument::partListChanged, partListWidget, &SkeletonPartListWidget::partListChanged);
connect(m_document, &SkeletonDocument::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged);
connect(graphicsWidget, &SkeletonGraphicsWidget::checkEdge, edgePropertyWidget, &SkeletonEdgePropertyWidget::showProperties);
connect(graphicsWidget, &SkeletonGraphicsWidget::uncheckEdge, [=](QUuid edgeId) {
edgePropertyWidget->hide();
});
connect(m_document, &SkeletonDocument::edgeRemoved, [=](QUuid edgeId) {
if (edgeId == edgePropertyWidget->currentEdgeId()) {
edgePropertyWidget->hide();
}
});
connect(edgePropertyWidget, &SkeletonEdgePropertyWidget::setEdgeBranchMode, m_document, &SkeletonDocument::setEdgeBranchMode);
connect(graphicsWidget, &SkeletonGraphicsWidget::checkNode, nodePropertyWidget, &SkeletonNodePropertyWidget::showProperties);
connect(graphicsWidget, &SkeletonGraphicsWidget::uncheckNode, [=](QUuid nodeId) {
nodePropertyWidget->hide();
});
connect(m_document, &SkeletonDocument::nodeRemoved, [=](QUuid nodeId) {
if (nodeId == nodePropertyWidget->currentNodeId()) {
nodePropertyWidget->hide();
}
});
connect(nodePropertyWidget, &SkeletonNodePropertyWidget::setNodeRootMarkMode, m_document, &SkeletonDocument::setNodeRootMarkMode);
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
connect(m_document, &SkeletonDocument::resultMeshChanged, [=]() {
m_modelWidget->updateMesh(m_document->takeResultMesh());
});
connect(graphicsWidget, &SkeletonGraphicsWidget::cursorChanged, [=]() {
m_modelWidget->setCursor(graphicsWidget->cursor());
});
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
}
SkeletonDocumentWindow::~SkeletonDocumentWindow()
{
}
void SkeletonDocumentWindow::showEvent(QShowEvent *event)
{
QMainWindow::showEvent(event);
if (m_firstShow) {
m_firstShow = false;
emit initialized();
}
}
void SkeletonDocumentWindow::changeTurnaround()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"),
QString(),
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
if (fileName.isEmpty())
return;
QImage image;
if (!image.load(fileName))
return;
m_document->updateTurnaround(image);
}

View File

@ -0,0 +1,27 @@
#ifndef SKELETON_DOCUMENT_WINDOW_H
#define SKELETON_DOCUMENT_WINDOW_H
#include <QMainWindow>
#include <QShowEvent>
#include "skeletondocument.h"
#include "modelwidget.h"
class SkeletonDocumentWindow : public QMainWindow
{
Q_OBJECT
signals:
void initialized();
public:
SkeletonDocumentWindow();
~SkeletonDocumentWindow();
protected:
void showEvent(QShowEvent *event);
public slots:
void changeTurnaround();
private:
SkeletonDocument *m_document;
bool m_firstShow;
ModelWidget *m_modelWidget;
};
#endif

View File

@ -0,0 +1,53 @@
#include <QFormLayout>
#include <QObject>
#include <QComboBox>
#include <QtGlobal>
#include "skeletonedgepropertywidget.h"
SkeletonEdgePropertyWidget::SkeletonEdgePropertyWidget(const SkeletonDocument *document) :
m_branchModeComboBox(nullptr),
m_document(document)
{
m_branchModeComboBox = new QComboBox;
m_branchModeComboBox->insertItem(0, tr("Auto"), (int)SkeletonEdgeBranchMode::Auto);
m_branchModeComboBox->insertItem(1, tr("No Trivial"), (int)SkeletonEdgeBranchMode::NoTrivial);
m_branchModeComboBox->insertItem(2, tr("Trivial"), (int)SkeletonEdgeBranchMode::Trivial);
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Joint Branch Mode:"), m_branchModeComboBox);
setLayout(formLayout);
hide();
connect(m_branchModeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
if (-1 != index) {
int mode = m_branchModeComboBox->itemData(index).toInt();
emit setEdgeBranchMode(m_edgeId, static_cast<SkeletonEdgeBranchMode>(mode));
}
});
}
void SkeletonEdgePropertyWidget::showProperties(QUuid edgeId)
{
m_edgeId = edgeId;
updateData();
show();
}
void SkeletonEdgePropertyWidget::updateData()
{
const SkeletonEdge *edge = m_document->findEdge(m_edgeId);
if (nullptr == edge) {
hide();
return;
}
int selectIndex = m_branchModeComboBox->findData((int)edge->branchMode);
m_branchModeComboBox->setCurrentIndex(selectIndex);
}
QUuid SkeletonEdgePropertyWidget::currentEdgeId()
{
return m_edgeId;
}

View File

@ -0,0 +1,26 @@
#ifndef SKELETON_EDGE_PROPERTY_WIDGET_H
#define SKELETON_EDGE_PROPERTY_WIDGET_H
#include <QWidget>
#include <QComboBox>
#include <QObject>
#include "skeletondocument.h"
class SkeletonEdgePropertyWidget : public QWidget
{
Q_OBJECT
signals:
void setEdgeBranchMode(QUuid edgeId, SkeletonEdgeBranchMode mode);
public slots:
void showProperties(QUuid edgeId);
public:
SkeletonEdgePropertyWidget(const SkeletonDocument *document);
QUuid currentEdgeId();
private:
void updateData();
private:
QComboBox *m_branchModeComboBox;
const SkeletonDocument *m_document;
QUuid m_edgeId;
};
#endif

View File

@ -1,74 +0,0 @@
#include <QPen>
#include "skeletoneditedgeitem.h"
#include "theme.h"
SkeletonEditEdgeItem::SkeletonEditEdgeItem(QGraphicsItem *parent) :
QGraphicsLineItem(parent),
m_firstNode(NULL),
m_secondNode(NULL)
{
setData(0, "edge");
m_checked = false;
updateAppearance();
}
void SkeletonEditEdgeItem::setChecked(bool checked)
{
if (m_checked == checked) {
return;
}
m_checked = checked;
updateAppearance();
}
bool SkeletonEditEdgeItem::checked()
{
return m_checked;
}
void SkeletonEditEdgeItem::updateAppearance()
{
QPen pen;
if (m_firstNode) {
QColor color = m_firstNode->sideColor();
color.setAlphaF(Theme::edgeAlpha);
pen.setColor(color);
} else if (m_secondNode) {
QColor color = m_secondNode->sideColor();
color.setAlphaF(Theme::edgeAlpha);
pen.setColor(color);
}
pen.setWidth(Theme::skeletonEdgeWidth);
setPen(pen);
}
void SkeletonEditEdgeItem::setNodes(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second)
{
m_firstNode = first;
m_secondNode = second;
updatePosition();
}
bool SkeletonEditEdgeItem::connects(SkeletonEditNodeItem *nodeItem)
{
return m_firstNode == nodeItem || m_secondNode == nodeItem;
}
void SkeletonEditEdgeItem::updatePosition()
{
if (m_firstNode && m_secondNode) {
QLineF line(m_firstNode->origin(), m_secondNode->origin());
setLine(line);
}
updateAppearance();
}
SkeletonEditNodeItem *SkeletonEditEdgeItem::firstNode()
{
return m_firstNode;
}
SkeletonEditNodeItem *SkeletonEditEdgeItem::secondNode()
{
return m_secondNode;
}

View File

@ -1,25 +0,0 @@
#ifndef SKELETON_EDIT_EDGE_ITEM_H
#define SKELETON_EDIT_EDGE_ITEM_H
#include <QGraphicsEllipseItem>
#include "skeletoneditnodeitem.h"
class SkeletonEditEdgeItem : public QGraphicsLineItem
{
public:
SkeletonEditEdgeItem(QGraphicsItem *parent = 0);
void setNodes(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second);
void updatePosition();
SkeletonEditNodeItem *firstNode();
SkeletonEditNodeItem *secondNode();
bool connects(SkeletonEditNodeItem *nodeItem);
void setChecked(bool checked);
bool checked();
private:
SkeletonEditNodeItem *m_firstNode;
SkeletonEditNodeItem *m_secondNode;
bool m_checked;
private:
void updateAppearance();
};
#endif

View File

@ -1,713 +0,0 @@
#include <QGraphicsPixmapItem>
#include <QXmlStreamWriter>
#include <QFile>
#include <QApplication>
#include <QGuiApplication>
#include <cmath>
#include <map>
#include <vector>
#include <assert.h>
#include "skeletoneditgraphicsview.h"
#include "skeletoneditnodeitem.h"
#include "skeletoneditedgeitem.h"
#include "theme.h"
qreal SkeletonEditGraphicsView::m_initialNodeSize = 128;
qreal SkeletonEditGraphicsView::m_minimalNodeSize = 8;
SkeletonEditGraphicsView::SkeletonEditGraphicsView(QWidget *parent) :
QGraphicsView(parent),
m_pendingNodeItem(NULL),
m_pendingEdgeItem(NULL),
m_inAddNodeMode(true),
m_nextStartNodeItem(NULL),
m_lastHoverNodeItem(NULL),
m_lastMousePos(0, 0),
m_isMovingNodeItem(false),
m_backgroundLoaded(false),
m_modelWidget(NULL),
m_modelWidgetProxy(NULL),
m_combineEnabled(true),
m_unionEnabled(true),
m_subdivEnabled(false)
{
setScene(new QGraphicsScene());
m_modelWidget = new ModelWidget(this);
m_modelWidget->setMinimumSize(128, 128);
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelWidget->move(100, 100);
m_modelWidget->setGraphicsView(this);
m_modelWidgetProxy = scene()->addWidget(m_modelWidget);
m_backgroundItem = new QGraphicsPixmapItem();
m_backgroundItem->setOpacity(0.25);
scene()->addItem(m_backgroundItem);
m_pendingNodeItem = new QGraphicsEllipseItem(0, 0, m_initialNodeSize, m_initialNodeSize);
m_pendingNodeItem->setVisible(false);
scene()->addItem(m_pendingNodeItem);
m_pendingEdgeItem = new QGraphicsLineItem(0, 0, 0, 0);
m_pendingEdgeItem->setVisible(false);
scene()->addItem(m_pendingEdgeItem);
}
ModelWidget *SkeletonEditGraphicsView::modelWidget()
{
return m_modelWidget;
}
void SkeletonEditGraphicsView::toggleAddNodeMode()
{
if (!m_backgroundLoaded)
return;
m_inAddNodeMode = !m_inAddNodeMode;
applyAddNodeMode();
}
void SkeletonEditGraphicsView::applyAddNodeMode()
{
m_pendingNodeItem->setVisible(m_inAddNodeMode);
m_pendingEdgeItem->setVisible(m_inAddNodeMode && m_nextStartNodeItem);
setMouseTracking(true);
}
void SkeletonEditGraphicsView::turnOffAddNodeMode()
{
if (!m_backgroundLoaded)
return;
m_inAddNodeMode = false;
applyAddNodeMode();
}
void SkeletonEditGraphicsView::turnOnAddNodeMode()
{
if (!m_backgroundLoaded)
return;
m_inAddNodeMode = true;
applyAddNodeMode();
}
SkeletonEditNodeItem *SkeletonEditGraphicsView::findNodeItemByPos(QPointF pos)
{
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "node") {
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
if (nodeItem->shape().contains(pos)) {
return nodeItem;
}
}
}
return NULL;
}
SkeletonEditEdgeItem *SkeletonEditGraphicsView::findEdgeItemByPos(QPointF pos)
{
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "edge") {
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
if (edgeItem->shape().contains(pos)) {
return edgeItem;
}
}
}
return NULL;
}
SkeletonEditEdgeItem *SkeletonEditGraphicsView::findEdgeItemByNodePair(SkeletonEditNodeItem *first,
SkeletonEditNodeItem *second)
{
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
assert(first != second);
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "edge") {
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
if ((edgeItem->firstNode() == first || edgeItem->secondNode() == first) &&
(edgeItem->firstNode() == second || edgeItem->secondNode() == second)) {
return edgeItem;
}
}
}
return NULL;
}
bool SkeletonEditGraphicsView::mousePress(QMouseEvent *event, const QPointF &scenePos)
{
bool processed = false;
if (!m_backgroundLoaded)
return false;
QPointF pos = scenePos;
if (event->button() == Qt::LeftButton) {
if (!m_inAddNodeMode) {
if (m_lastHoverNodeItem) {
setNextStartNodeItem(m_lastHoverNodeItem);
m_lastHoverNodeItem = NULL;
processed = true;
} else {
if (m_nextStartNodeItem) {
setNextStartNodeItem(NULL);
processed = true;
}
}
}
}
m_lastMousePos = pos;
return processed;
}
bool SkeletonEditGraphicsView::mouseDoubleClick(QMouseEvent *event, const QPointF &scenePos)
{
bool processed = false;
if (QApplication::keyboardModifiers() & Qt::ControlModifier) {
processed = true;
emit changeTurnaroundTriggered();
}
return processed;
}
void SkeletonEditGraphicsView::removeSelectedItems()
{
if (m_nextStartNodeItem) {
SkeletonEditNodeItem *nodeItem = m_nextStartNodeItem;
setNextStartNodeItem(NULL);
removeNodeItem(nodeItem->nextSidePair());
removeNodeItem(nodeItem);
emit nodesChanged();
}
}
void SkeletonEditGraphicsView::removeNodeItem(SkeletonEditNodeItem *nodeItem)
{
scene()->removeItem(nodeItem);
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "edge") {
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
if (edgeItem->firstNode() == nodeItem || edgeItem->secondNode() == nodeItem) {
scene()->removeItem(edgeItem);
}
}
}
}
void SkeletonEditGraphicsView::fetchNodeItemAndAllSidePairs(SkeletonEditNodeItem *nodeItem, std::vector<SkeletonEditNodeItem *> *sidePairs)
{
sidePairs->push_back(nodeItem);
sidePairs->push_back(nodeItem->nextSidePair());
sidePairs->push_back(nodeItem->nextSidePair()->nextSidePair());
}
void SkeletonEditGraphicsView::removeNodeItemAndSidePairs(SkeletonEditNodeItem *nodeItem)
{
std::vector<SkeletonEditNodeItem *> nodes;
fetchNodeItemAndAllSidePairs(nodeItem, &nodes);
for (size_t i = 0; i < nodes.size(); i++) {
removeNodeItem(nodes[i]);
}
}
bool SkeletonEditGraphicsView::keyPress(QKeyEvent *event, const QPointF &scenePos)
{
bool processed = false;
if (!m_backgroundLoaded)
return false;
if (event->key() == Qt::Key_A) {
toggleAddNodeMode();
processed = true;
} else if (event->key() == Qt::Key_C) {
m_combineEnabled = !m_combineEnabled;
emit nodesChanged();
} else if (event->key() == Qt::Key_U) {
m_unionEnabled = !m_unionEnabled;
emit nodesChanged();
} else if (event->key() == Qt::Key_S) {
m_subdivEnabled = !m_subdivEnabled;
emit nodesChanged();
} else if (event->key() == Qt::Key_Tab) {
if (m_nextStartNodeItem)
setNextStartNodeItem(m_nextStartNodeItem->nextSidePair());
} else if (event->key() == Qt::Key_Delete || event->key() ==Qt::Key_Backspace) {
removeSelectedItems();
processed = true;
}
return processed;
}
void SkeletonEditGraphicsView::resizeEvent(QResizeEvent *event)
{
QFrame::resizeEvent(event);
emit sizeChanged();
}
SkeletonEditNodeItem *SkeletonEditGraphicsView::addNodeItemAndSidePairs(QRectF area, SkeletonEditNodeItem *fromNodeItem, const QString &sideColorName)
{
float pairedX = 0;
QRectF pairedRect = area;
if (fromNodeItem) {
pairedX = fromNodeItem->nextSidePair()->rect().x();
} else {
if (area.center().x() < scene()->sceneRect().width() / 2) {
pairedX = area.center().x() + scene()->sceneRect().width() / 4;
} else {
pairedX = area.center().x() - scene()->sceneRect().width() / 4;
}
}
pairedRect.translate(pairedX - area.x(), 0);
SkeletonEditNodeItem *firstNode = new SkeletonEditNodeItem(area);
scene()->addItem(firstNode);
firstNode->setSideColorName(fromNodeItem ? fromNodeItem->sideColorName() : sideColorName);
SkeletonEditNodeItem *secondNode = new SkeletonEditNodeItem(pairedRect);
scene()->addItem(secondNode);
secondNode->setSideColorName(firstNode->nextSideColorName());
firstNode->setNextSidePair(secondNode);
secondNode->setNextSidePair(firstNode);
setNextStartNodeItem(firstNode);
if (!fromNodeItem) {
return firstNode;
}
addEdgeItem(fromNodeItem, firstNode);
addEdgeItem(fromNodeItem->nextSidePair(), firstNode->nextSidePair());
return firstNode;
}
SkeletonEditNodeItem *SkeletonEditGraphicsView::addNodeItem(float originX, float originY, float radius)
{
QRectF area(originX - radius, originY - radius, radius * 2, radius * 2);
SkeletonEditNodeItem *firstNode = new SkeletonEditNodeItem(area);
scene()->addItem(firstNode);
return firstNode;
}
void SkeletonEditGraphicsView::addEdgeItem(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second)
{
SkeletonEditEdgeItem *newEdge = new SkeletonEditEdgeItem();
newEdge->setNodes(first, second);
scene()->addItem(newEdge);
}
bool SkeletonEditGraphicsView::mouseRelease(QMouseEvent *event, const QPointF &scenePos)
{
bool processed = false;
if (!m_backgroundLoaded)
return false;
if (event->button() == Qt::LeftButton) {
if (m_inAddNodeMode) {
if (m_lastHoverNodeItem && m_nextStartNodeItem &&
m_lastHoverNodeItem != m_nextStartNodeItem &&
m_lastHoverNodeItem->sideColor() == m_nextStartNodeItem->sideColor()) {
if (!findEdgeItemByNodePair(m_lastHoverNodeItem, m_nextStartNodeItem)) {
addEdgeItem(m_nextStartNodeItem, m_lastHoverNodeItem);
addEdgeItem(m_nextStartNodeItem->nextSidePair(), m_lastHoverNodeItem->nextSidePair());
processed = true;
emit nodesChanged();
}
} else {
addNodeItemAndSidePairs(m_pendingNodeItem->rect(), m_nextStartNodeItem);
processed = true;
emit nodesChanged();
}
}
m_isMovingNodeItem = false;
}
return processed;
}
bool SkeletonEditGraphicsView::canNodeItemMoveTo(SkeletonEditNodeItem *item, QPointF moveTo)
{
if (moveTo.x() < 0)
return false;
if (moveTo.y() < 0)
return false;
if (moveTo.x() + item->rect().width() >= scene()->sceneRect().width())
return false;
if (moveTo.y() + item->rect().height() >= scene()->sceneRect().height())
return false;
return true;
}
bool SkeletonEditGraphicsView::mouseMove(QMouseEvent *event, const QPointF &scenePos)
{
bool processed = false;
if (!m_backgroundLoaded)
return false;
QPointF pos = scenePos;
QPointF moveTo = QPointF(pos.x() - m_pendingNodeItem->rect().width() / 2, pos.y() - m_pendingNodeItem->rect().height() / 2);
if (moveTo.x() < 0)
moveTo.setX(0);
if (moveTo.y() < 0)
moveTo.setY(0);
if (moveTo.x() + m_pendingNodeItem->rect().width() >= scene()->sceneRect().width())
moveTo.setX(scene()->sceneRect().width() - m_pendingNodeItem->rect().width());
if (moveTo.y() + m_pendingNodeItem->rect().height() >= scene()->sceneRect().height())
moveTo.setY(scene()->sceneRect().height() - m_pendingNodeItem->rect().height());
QSizeF oldSize = m_pendingNodeItem->rect().size();
m_pendingNodeItem->setRect(moveTo.x(), moveTo.y(),
oldSize.width(),
oldSize.height());
if (m_nextStartNodeItem) {
m_pendingEdgeItem->setLine(QLineF(m_nextStartNodeItem->origin(), QPointF(moveTo.x() + m_pendingNodeItem->rect().width() / 2,
moveTo.y() + m_pendingNodeItem->rect().height() / 2)));
}
if (!m_isMovingNodeItem) {
SkeletonEditNodeItem *hoverNodeItem = findNodeItemByPos(pos);
if (hoverNodeItem) {
hoverNodeItem->setHovered(true);
processed = true;
}
if (hoverNodeItem != m_lastHoverNodeItem) {
if (m_lastHoverNodeItem)
m_lastHoverNodeItem->setHovered(false);
m_lastHoverNodeItem = hoverNodeItem;
processed = true;
}
}
QPointF curMousePos = pos;
if (m_lastHoverNodeItem) {
if ((event->buttons() & Qt::LeftButton) &&
(curMousePos != m_lastMousePos || m_isMovingNodeItem)) {
m_isMovingNodeItem = true;
QRectF rect = m_lastHoverNodeItem->rect();
QRectF pairedRect;
rect.translate(curMousePos.x() - m_lastMousePos.x(), curMousePos.y() - m_lastMousePos.y());
pairedRect = m_lastHoverNodeItem->nextSidePair()->rect();
pairedRect.translate(0, curMousePos.y() - m_lastMousePos.y());
m_lastHoverNodeItem->setRect(rect);
m_lastHoverNodeItem->nextSidePair()->setRect(pairedRect);
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "edge") {
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
if (edgeItem->connects(m_lastHoverNodeItem) ||
edgeItem->connects(m_lastHoverNodeItem->nextSidePair()))
edgeItem->updatePosition();
}
}
processed = true;
emit nodesChanged();
}
}
m_lastMousePos = curMousePos;
return processed;
}
void SkeletonEditGraphicsView::AddItemRadius(QGraphicsEllipseItem *item, float delta)
{
QSizeF oldSize = item->rect().size();
QPointF originPt = QPointF(item->rect().left() + oldSize.width() / 2,
item->rect().top() + oldSize.height() / 2);
QSizeF newSize = QSizeF(oldSize.width() + delta, oldSize.height() + delta);
if (newSize.width() < m_minimalNodeSize || newSize.height() < m_minimalNodeSize) {
newSize.setWidth(m_minimalNodeSize);
newSize.setHeight(m_minimalNodeSize);
}
QPointF newLeftTop = QPointF(originPt.x() - newSize.width() / 2,
originPt.y() - newSize.height() / 2);
if (newLeftTop.x() < 0 || newLeftTop.x() + newSize.width() >= scene()->sceneRect().width())
return;
if (newLeftTop.y() < 0 || newLeftTop.y() + newSize.height() >= scene()->sceneRect().height())
return;
item->setRect(newLeftTop.x(),
newLeftTop.y(),
newSize.width(),
newSize.height());
}
bool SkeletonEditGraphicsView::canAddItemRadius(QGraphicsEllipseItem *item, float delta)
{
QSizeF oldSize = item->rect().size();
QPointF originPt = QPointF(item->rect().left() + oldSize.width() / 2,
item->rect().top() + oldSize.height() / 2);
QSizeF newSize = QSizeF(oldSize.width() + delta, oldSize.height() + delta);
if (newSize.width() < m_minimalNodeSize || newSize.height() < m_minimalNodeSize) {
newSize.setWidth(m_minimalNodeSize);
newSize.setHeight(m_minimalNodeSize);
}
QPointF newLeftTop = QPointF(originPt.x() - newSize.width() / 2,
originPt.y() - newSize.height() / 2);
if (newLeftTop.x() < 0 || newLeftTop.x() + newSize.width() >= scene()->sceneRect().width())
return false;
if (newLeftTop.y() < 0 || newLeftTop.y() + newSize.height() >= scene()->sceneRect().height())
return false;
return true;
}
bool SkeletonEditGraphicsView::wheel(QWheelEvent *event, const QPointF &scenePos)
{
bool processed = false;
if (!m_backgroundLoaded)
return false;
qreal delta = event->delta() / 10;
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
if (delta > 0)
delta = 1;
else
delta = -1;
} else {
if (fabs(delta) < 1)
delta = delta < 0 ? -1.0 : 1.0;
}
AddItemRadius(m_pendingNodeItem, delta);
if (!m_inAddNodeMode && m_lastHoverNodeItem) {
AddItemRadius(m_lastHoverNodeItem, delta);
AddItemRadius(m_lastHoverNodeItem->nextSidePair(), delta);
processed = true;
emit nodesChanged();
}
if (m_inAddNodeMode)
processed = true;
return processed;
}
void SkeletonEditGraphicsView::setNextStartNodeItem(SkeletonEditNodeItem *item)
{
if (m_nextStartNodeItem != item) {
if (m_nextStartNodeItem)
m_nextStartNodeItem->setChecked(false);
}
m_nextStartNodeItem = item;
if (m_nextStartNodeItem)
m_nextStartNodeItem->setChecked(true);
applyAddNodeMode();
}
void SkeletonEditGraphicsView::updateBackgroundImage(const QImage &image)
{
QSizeF oldSceneSize = scene()->sceneRect().size();
QPixmap pixmap = QPixmap::fromImage(image);
scene()->setSceneRect(pixmap.rect());
m_backgroundItem->setPixmap(pixmap);
adjustItems(oldSceneSize, scene()->sceneRect().size());
if (!m_backgroundLoaded) {
m_backgroundLoaded = true;
applyAddNodeMode();
}
}
QPixmap SkeletonEditGraphicsView::backgroundImage()
{
return m_backgroundItem->pixmap();
}
bool SkeletonEditGraphicsView::hasBackgroundImage()
{
return m_backgroundLoaded;
}
void SkeletonEditGraphicsView::adjustItems(QSizeF oldSceneSize, QSizeF newSceneSize)
{
if (oldSceneSize == newSceneSize)
return;
float radiusMul = (float)newSceneSize.height() / oldSceneSize.height();
float xMul = (float)newSceneSize.width() / oldSceneSize.width();
float yMul = radiusMul;
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "node") {
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
nodeItem->setRadius(nodeItem->radius() * radiusMul);
QPointF oldOrigin = nodeItem->origin();
nodeItem->setOrigin(QPointF(oldOrigin.x() * xMul, oldOrigin.y() * yMul));
}
}
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "edge") {
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
edgeItem->updatePosition();
}
}
}
void SkeletonEditGraphicsView::loadFromSnapshot(SkeletonSnapshot *snapshot)
{
float radiusMul = 1.0;
float xMul = 1.0;
float yMul = radiusMul;
QString canvasWidth = snapshot->canvas["width"];
QString canvasHeight = snapshot->canvas["height"];
float canvasWidthVal = canvasWidth.toFloat();
float canvasHeightVal = canvasHeight.toFloat();
if (!hasBackgroundImage()) {
QPixmap emptyImage((int)canvasWidthVal, (int)canvasHeightVal);
emptyImage.fill(QWidget::palette().color(QWidget::backgroundRole()));
updateBackgroundImage(emptyImage.toImage());
}
if (canvasHeightVal > 0)
radiusMul = (float)scene()->sceneRect().height() / canvasHeightVal;
if (canvasWidthVal > 0)
xMul = (float)scene()->sceneRect().width() / canvasWidthVal;
yMul = radiusMul;
std::map<QString, SkeletonEditNodeItem *> nodeItemMap;
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) {
std::map<QString, QString> *snapshotNode = &nodeIterator->second;
SkeletonEditNodeItem *nodeItem = addNodeItem((*snapshotNode)["x"].toFloat() * xMul,
(*snapshotNode)["y"].toFloat() * yMul,
(*snapshotNode)["radius"].toFloat() * radiusMul);
nodeItem->setSideColorName((*snapshotNode)["sideColorName"]);
nodeItem->markAsBranch("true" == (*snapshotNode)["isBranch"]);
nodeItemMap[nodeIterator->first] = nodeItem;
}
for (nodeIterator = snapshot->nodes.begin(); nodeIterator != snapshot->nodes.end(); nodeIterator++) {
std::map<QString, QString> *snapshotNode = &nodeIterator->second;
SkeletonEditNodeItem *nodeItem = nodeItemMap[nodeIterator->first];
nodeItem->setNextSidePair(nodeItemMap[(*snapshotNode)["nextSidePair"]]);
}
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
for (edgeIterator = snapshot->edges.begin(); edgeIterator != snapshot->edges.end(); edgeIterator++) {
std::map<QString, QString> *snapshotEdge = &edgeIterator->second;
addEdgeItem(nodeItemMap[(*snapshotEdge)["from"]], nodeItemMap[(*snapshotEdge)["to"]]);
}
emit nodesChanged();
}
void SkeletonEditGraphicsView::saveToSnapshot(SkeletonSnapshot *snapshot)
{
snapshot->canvas["width"] = QString("%1").arg(scene()->sceneRect().width());
snapshot->canvas["height"] = QString("%1").arg(scene()->sceneRect().height());
snapshot->canvas["combine"] = m_combineEnabled ? "true" : "false";
snapshot->canvas["union"] = m_unionEnabled ? "true" : "false";
snapshot->canvas["subdiv"] = m_subdivEnabled ? "true" : "false";
QList<QGraphicsItem *>::iterator it;
QList<QGraphicsItem *> list = scene()->items();
int nextNodeId = 1;
std::map<SkeletonEditNodeItem *, QString> nodeIdMap;
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "node") {
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
QString nodeId = QString("node%1").arg(nextNodeId);
std::map<QString, QString> *snapshotNode = &snapshot->nodes[nodeId];
(*snapshotNode)["id"] = nodeId;
(*snapshotNode)["sideColorName"] = nodeItem->sideColorName();
(*snapshotNode)["radius"] = QString("%1").arg(nodeItem->radius());
(*snapshotNode)["isBranch"] = QString("%1").arg(nodeItem->isBranch() ? "true" : "false");
(*snapshotNode)["isRoot"] = QString("%1").arg(nodeItem->isRoot() ? "true" : "false");
QPointF origin = nodeItem->origin();
(*snapshotNode)["x"] = QString("%1").arg(origin.x());
(*snapshotNode)["y"] = QString("%1").arg(origin.y());
nodeIdMap[nodeItem] = nodeId;
nextNodeId++;
}
}
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "node") {
SkeletonEditNodeItem *nodeItem = static_cast<SkeletonEditNodeItem *>(*it);
QString nodeId = nodeIdMap[nodeItem];
std::map<QString, QString> *snapshotNode = &snapshot->nodes[nodeId];
(*snapshotNode)["nextSidePair"] = nodeIdMap[nodeItem->nextSidePair()];
}
}
int nextEdgeId = 1;
for (it = list.begin(); it != list.end(); ++it) {
if ((*it)->data(0).toString() == "edge") {
SkeletonEditEdgeItem *edgeItem = static_cast<SkeletonEditEdgeItem *>(*it);
QString edgeId = QString("edge%1").arg(nextEdgeId);
std::map<QString, QString> *snapshotEdge = &snapshot->edges[edgeId];
(*snapshotEdge)["id"] = edgeId;
(*snapshotEdge)["from"] = nodeIdMap[edgeItem->firstNode()];
(*snapshotEdge)["to"] = nodeIdMap[edgeItem->secondNode()];
nextEdgeId++;
}
}
}
void SkeletonEditGraphicsView::markAsBranch()
{
if (m_nextStartNodeItem) {
m_nextStartNodeItem->markAsBranch(true);
m_nextStartNodeItem->nextSidePair()->markAsBranch(true);
emit nodesChanged();
}
}
void SkeletonEditGraphicsView::markAsTrunk()
{
if (m_nextStartNodeItem) {
m_nextStartNodeItem->markAsBranch(false);
m_nextStartNodeItem->nextSidePair()->markAsBranch(false);
emit nodesChanged();
}
}
void SkeletonEditGraphicsView::markAsRoot()
{
if (m_nextStartNodeItem) {
m_nextStartNodeItem->markAsRoot(true);
m_nextStartNodeItem->nextSidePair()->markAsRoot(true);
emit nodesChanged();
}
}
void SkeletonEditGraphicsView::markAsChild()
{
if (m_nextStartNodeItem) {
m_nextStartNodeItem->markAsRoot(false);
m_nextStartNodeItem->nextSidePair()->markAsRoot(false);
emit nodesChanged();
}
}
void SkeletonEditGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
mouseMove(event, mapToScene(event->pos()));
}
void SkeletonEditGraphicsView::wheelEvent(QWheelEvent *event)
{
wheel(event, mapToScene(event->pos()));
}
void SkeletonEditGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
mouseRelease(event, mapToScene(event->pos()));
}
void SkeletonEditGraphicsView::mousePressEvent(QMouseEvent *event)
{
mousePress(event, mapToScene(event->pos()));
}
void SkeletonEditGraphicsView::mouseDoubleClickEvent(QMouseEvent *event)
{
mouseDoubleClick(event, mapToScene(event->pos()));
}
void SkeletonEditGraphicsView::keyPressEvent(QKeyEvent *event)
{
keyPress(event, QPointF());
}
bool SkeletonEditGraphicsView::combineEnabled()
{
return m_combineEnabled;
}
bool SkeletonEditGraphicsView::unionEnabled()
{
return m_unionEnabled;
}
bool SkeletonEditGraphicsView::subdivEnabled()
{
return m_subdivEnabled;
}

View File

@ -1,93 +0,0 @@
#ifndef SKELETON_EDIT_GRAPHICS_VIEW_H
#define SKELETON_EDIT_GRAPHICS_VIEW_H
#include <QGraphicsView>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QKeyEvent>
#include <QXmlStreamWriter>
#include <QXmlStreamReader>
#include <QGraphicsProxyWidget>
#include "skeletoneditnodeitem.h"
#include "skeletoneditedgeitem.h"
#include "skeletonsnapshot.h"
#include "modelwidget.h"
class SkeletonEditGraphicsView : public QGraphicsView
{
Q_OBJECT
signals:
void sizeChanged();
void nodesChanged();
void changeTurnaroundTriggered();
void edgeCheckStateChanged();
public slots:
void turnOffAddNodeMode();
void turnOnAddNodeMode();
void markAsBranch();
void markAsTrunk();
void markAsRoot();
void markAsChild();
bool mouseMove(QMouseEvent *event, const QPointF &scenePos);
bool wheel(QWheelEvent *event, const QPointF &scenePos);
bool mouseRelease(QMouseEvent *event, const QPointF &scenePos);
bool mousePress(QMouseEvent *event, const QPointF &scenePos);
bool mouseDoubleClick(QMouseEvent *event, const QPointF &scenePos);
bool keyPress(QKeyEvent *event, const QPointF &scenePos);
public:
SkeletonEditGraphicsView(QWidget *parent = 0);
void updateBackgroundImage(const QImage &image);
bool hasBackgroundImage();
QPixmap backgroundImage();
void saveToSnapshot(SkeletonSnapshot *snapshot);
void loadFromSnapshot(SkeletonSnapshot *snapshot);
ModelWidget *modelWidget();
bool combineEnabled();
bool unionEnabled();
bool subdivEnabled();
protected:
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void resizeEvent(QResizeEvent *event);
private:
QGraphicsPixmapItem *m_backgroundItem;
QGraphicsEllipseItem *m_pendingNodeItem;
QGraphicsLineItem *m_pendingEdgeItem;
static qreal m_initialNodeSize;
static qreal m_minimalNodeSize;
bool m_inAddNodeMode;
SkeletonEditNodeItem *m_nextStartNodeItem;
SkeletonEditNodeItem *m_lastHoverNodeItem;
QPointF m_lastMousePos;
bool m_isMovingNodeItem;
bool m_backgroundLoaded;
ModelWidget *m_modelWidget;
QGraphicsProxyWidget *m_modelWidgetProxy;
bool m_combineEnabled;
bool m_unionEnabled;
bool m_subdivEnabled;
private:
void toggleAddNodeMode();
void applyAddNodeMode();
SkeletonEditNodeItem *findNodeItemByPos(QPointF pos);
SkeletonEditEdgeItem *findEdgeItemByPos(QPointF pos);
SkeletonEditEdgeItem *findEdgeItemByNodePair(SkeletonEditNodeItem *first,
SkeletonEditNodeItem *second);
void setNextStartNodeItem(SkeletonEditNodeItem *item);
bool canNodeItemMoveTo(SkeletonEditNodeItem *item, QPointF moveTo);
void AddItemRadius(QGraphicsEllipseItem *item, float delta);
bool canAddItemRadius(QGraphicsEllipseItem *item, float delta);
void adjustItems(QSizeF oldSceneSize, QSizeF newSceneSize);
void removeSelectedItems();
void removeNodeItem(SkeletonEditNodeItem *nodeItem);
void removeNodeItemAndSidePairs(SkeletonEditNodeItem *nodeItem);
void fetchNodeItemAndAllSidePairs(SkeletonEditNodeItem *nodeItem, std::vector<SkeletonEditNodeItem *> *sidePairs);
SkeletonEditNodeItem *addNodeItemAndSidePairs(QRectF area, SkeletonEditNodeItem *fromNodeItem, const QString &sideColorName="red");
SkeletonEditNodeItem *addNodeItem(float originX, float originY, float radius);
void addEdgeItem(SkeletonEditNodeItem *first, SkeletonEditNodeItem *second);
};
#endif

View File

@ -1,140 +0,0 @@
#include <QPen>
#include "skeletoneditnodeitem.h"
#include "theme.h"
SkeletonEditNodeItem::SkeletonEditNodeItem(const QRectF &rect, QGraphicsItem *parent) :
QGraphicsEllipseItem(rect, parent),
m_hovered(false),
m_checked(false),
m_nextSidePair(NULL),
m_sideColor(Theme::red),
m_sideColorName("red"),
m_isBranch(false),
m_isRoot(false)
{
setData(0, "node");
updateAppearance();
}
void SkeletonEditNodeItem::markAsBranch(bool isBranch)
{
m_isBranch = isBranch;
updateAppearance();
}
bool SkeletonEditNodeItem::isBranch()
{
return m_isBranch;
}
void SkeletonEditNodeItem::markAsRoot(bool isRoot)
{
m_isRoot = isRoot;
updateAppearance();
}
bool SkeletonEditNodeItem::isRoot()
{
return m_isRoot;
}
const QString &SkeletonEditNodeItem::sideColorName()
{
return m_sideColorName;
}
QString SkeletonEditNodeItem::nextSideColorName()
{
return Theme::nextSideColorNameMap[m_sideColorName];
}
void SkeletonEditNodeItem::setSideColorName(const QString &name)
{
m_sideColorName = name;
m_sideColor = Theme::sideColorNameToColorMap[m_sideColorName];
updateAppearance();
}
bool SkeletonEditNodeItem::hovered()
{
return m_hovered;
}
void SkeletonEditNodeItem::setHovered(bool hovered)
{
m_hovered = hovered;
updateAppearance();
}
bool SkeletonEditNodeItem::checked()
{
return m_checked;
}
void SkeletonEditNodeItem::setChecked(bool checked)
{
m_checked = checked;
updateAppearance();
}
SkeletonEditNodeItem *SkeletonEditNodeItem::nextSidePair()
{
return m_nextSidePair;
}
void SkeletonEditNodeItem::setNextSidePair(SkeletonEditNodeItem *nodeItem)
{
m_nextSidePair = nodeItem;
updateAppearance();
}
const QColor &SkeletonEditNodeItem::sideColor()
{
return m_sideColor;
}
QPointF SkeletonEditNodeItem::origin()
{
return QPointF(rect().x() + rect().width() / 2,
rect().y() + rect().height() / 2);
}
float SkeletonEditNodeItem::radius()
{
return rect().width() / 2;
}
void SkeletonEditNodeItem::setRadius(float radius)
{
QPointF oldOrigin = origin();
setRect(oldOrigin.x() - radius, oldOrigin.y() - radius,
radius * 2, radius * 2);
}
void SkeletonEditNodeItem::setOrigin(QPointF point)
{
QPointF moveBy = point - origin();
QRectF newRect = rect();
newRect.adjust(moveBy.x(), moveBy.y(), moveBy.x(), moveBy.y());
setRect(newRect);
}
void SkeletonEditNodeItem::updateAppearance()
{
QColor penColor = m_sideColor;
penColor.setAlphaF(m_checked ? Theme::checkedAlpha : (m_isBranch ? Theme::branchAlpha : Theme::normalAlpha));
QPen pen(penColor);
pen.setWidth(m_isRoot ? (Theme::skeletonNodeBorderSize * 2) : Theme::skeletonNodeBorderSize);
setPen(pen);
QColor brushColor = m_sideColor;
brushColor.setAlphaF((m_hovered || m_checked) ? Theme::fillAlpha : 0);
QBrush brush(brushColor);
setBrush(brush);
}
QColor SkeletonEditNodeItem::nextSideColor()
{
return Theme::sideColorNameToColorMap[Theme::nextSideColorNameMap[m_sideColorName]];
}

View File

@ -1,46 +0,0 @@
#ifndef SKELETON_EDIT_NODE_ITEM_H
#define SKELETON_EDIT_NODE_ITEM_H
#include <QGraphicsEllipseItem>
#include <map>
#include <QString>
#include <QColor>
class SkeletonEditEdgeItem;
class SkeletonEditNodeItem : public QGraphicsEllipseItem
{
public:
SkeletonEditNodeItem(const QRectF &rect, QGraphicsItem *parent = 0);
QPointF origin();
void setOrigin(QPointF point);
float radius();
void setRadius(float radius);
bool hovered();
void setHovered(bool hovered);
bool checked();
void setChecked(bool checked);
void markAsBranch(bool isBranch);
bool isBranch();
void markAsRoot(bool isRoot);
bool isRoot();
SkeletonEditNodeItem *nextSidePair();
void setNextSidePair(SkeletonEditNodeItem *nodeItem);
const QColor &sideColor();
QColor nextSideColor();
const QString &sideColorName();
QString nextSideColorName();
void setSideColorName(const QString &name);
private:
bool m_hovered;
bool m_checked;
SkeletonEditNodeItem *m_nextSidePair;
QColor m_sideColor;
QString m_sideColorName;
bool m_isBranch;
bool m_isRoot;
private:
void updateAppearance();
};
#endif

View File

@ -0,0 +1,644 @@
#include <QDebug>
#include <QScrollBar>
#include <QGuiApplication>
#include <cmath>
#include <QtGlobal>
#include <algorithm>
#include "skeletongraphicswidget.h"
#include "theme.h"
#include "util.h"
SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document) :
m_document(document),
m_turnaroundChanged(false),
m_turnaroundLoader(nullptr),
m_dragStarted(false),
m_cursorNodeItem(nullptr),
m_cursorEdgeItem(nullptr),
m_checkedNodeItem(nullptr),
m_moveStarted(false),
m_hoveredNodeItem(nullptr),
m_hoveredEdgeItem(nullptr),
m_checkedEdgeItem(nullptr),
m_lastAddedX(0),
m_lastAddedY(0),
m_lastAddedZ(0)
{
setRenderHint(QPainter::Antialiasing, false);
setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
setContentsMargins(0, 0, 0, 0);
setFrameStyle(QFrame::NoFrame);
setAlignment(Qt::AlignCenter);
setScene(new QGraphicsScene());
m_backgroundItem = new QGraphicsPixmapItem();
m_backgroundItem->setOpacity(0.25);
scene()->addItem(m_backgroundItem);
m_cursorNodeItem = new SkeletonGraphicsNodeItem();
m_cursorNodeItem->hide();
scene()->addItem(m_cursorNodeItem);
m_cursorEdgeItem = new SkeletonGraphicsEdgeItem();
m_cursorEdgeItem->hide();
scene()->addItem(m_cursorEdgeItem);
scene()->setSceneRect(rect());
setMouseTracking(true);
}
void SkeletonGraphicsWidget::updateItems()
{
for (auto nodeItemIt = nodeItemMap.begin(); nodeItemIt != nodeItemMap.end(); nodeItemIt++) {
nodeRadiusChanged(nodeItemIt->first);
nodeOriginChanged(nodeItemIt->first);
}
}
void SkeletonGraphicsWidget::canvasResized()
{
updateTurnaround();
}
void SkeletonGraphicsWidget::turnaroundChanged()
{
updateTurnaround();
setTransform(QTransform());
}
void SkeletonGraphicsWidget::updateTurnaround()
{
if (m_document->turnaround.isNull())
return;
m_turnaroundChanged = true;
if (m_turnaroundLoader)
return;
qDebug() << "Fit turnaround to view size:" << parentWidget()->rect().size();
m_turnaroundChanged = false;
QThread *thread = new QThread;
m_turnaroundLoader = new TurnaroundLoader(m_document->turnaround,
parentWidget()->rect().size());
m_turnaroundLoader->moveToThread(thread);
connect(thread, SIGNAL(started()), m_turnaroundLoader, SLOT(process()));
connect(m_turnaroundLoader, SIGNAL(finished()), this, SLOT(turnaroundImageReady()));
connect(m_turnaroundLoader, SIGNAL(finished()), thread, SLOT(quit()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
void SkeletonGraphicsWidget::turnaroundImageReady()
{
QImage *backgroundImage = m_turnaroundLoader->takeResultImage();
if (backgroundImage && backgroundImage->width() > 0 && backgroundImage->height() > 0) {
qDebug() << "Fit turnaround finished with image size:" << backgroundImage->size();
setFixedSize(backgroundImage->size());
scene()->setSceneRect(rect());
m_backgroundItem->setPixmap(QPixmap::fromImage(*backgroundImage));
updateItems();
} else {
qDebug() << "Fit turnaround failed";
}
delete backgroundImage;
delete m_turnaroundLoader;
m_turnaroundLoader = nullptr;
if (m_turnaroundChanged) {
updateTurnaround();
}
}
void SkeletonGraphicsWidget::updateCursor()
{
if (SkeletonDocumentEditMode::Add != m_document->editMode) {
m_cursorEdgeItem->hide();
m_cursorNodeItem->hide();
}
switch (m_document->editMode) {
case SkeletonDocumentEditMode::Add:
setCursor(QCursor(Theme::awesome()->icon(fa::plus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
break;
case SkeletonDocumentEditMode::Select:
setCursor(QCursor(Theme::awesome()->icon(fa::mousepointer).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
break;
case SkeletonDocumentEditMode::Drag:
setCursor(QCursor(Theme::awesome()->icon(m_dragStarted ? fa::handrocko : fa::handpapero).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
break;
case SkeletonDocumentEditMode::ZoomIn:
setCursor(QCursor(Theme::awesome()->icon(fa::searchplus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
break;
case SkeletonDocumentEditMode::ZoomOut:
setCursor(QCursor(Theme::awesome()->icon(fa::searchminus).pixmap(Theme::toolIconFontSize, Theme::toolIconFontSize)));
break;
}
emit cursorChanged();
}
void SkeletonGraphicsWidget::editModeChanged()
{
if (m_checkedNodeItem) {
if (!m_checkedNodeItem->checked()) {
if (m_hoveredNodeItem == m_checkedNodeItem) {
m_checkedNodeItem->setHovered(false);
m_hoveredNodeItem = nullptr;
}
m_checkedNodeItem = nullptr;
}
}
updateCursor();
}
void SkeletonGraphicsWidget::mouseMoveEvent(QMouseEvent *event)
{
QGraphicsView::mouseMoveEvent(event);
mouseMove(event);
}
void SkeletonGraphicsWidget::wheelEvent(QWheelEvent *event)
{
if (SkeletonDocumentEditMode::ZoomIn == m_document->editMode ||
SkeletonDocumentEditMode::ZoomOut == m_document->editMode ||
SkeletonDocumentEditMode::Drag == m_document->editMode)
QGraphicsView::wheelEvent(event);
wheel(event);
}
void SkeletonGraphicsWidget::mouseReleaseEvent(QMouseEvent *event)
{
QGraphicsView::mouseReleaseEvent(event);
mouseRelease(event);
}
void SkeletonGraphicsWidget::mousePressEvent(QMouseEvent *event)
{
QGraphicsView::mousePressEvent(event);
mousePress(event);
}
void SkeletonGraphicsWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
QGraphicsView::mouseDoubleClickEvent(event);
mouseDoubleClick(event);
}
void SkeletonGraphicsWidget::keyPressEvent(QKeyEvent *event)
{
QGraphicsView::keyPressEvent(event);
keyPress(event);
}
bool SkeletonGraphicsWidget::mouseMove(QMouseEvent *event)
{
if (m_dragStarted) {
QPoint currentGlobalPos = event->globalPos();
if (verticalScrollBar())
verticalScrollBar()->setValue(verticalScrollBar()->value() + m_lastGlobalPos.y() - currentGlobalPos.y());
if (horizontalScrollBar())
horizontalScrollBar()->setValue(horizontalScrollBar()->value() + m_lastGlobalPos.x() - currentGlobalPos.x());
m_lastGlobalPos = currentGlobalPos;
return true;
}
if (SkeletonDocumentEditMode::Select == m_document->editMode ||
SkeletonDocumentEditMode::Add == m_document->editMode) {
SkeletonGraphicsNodeItem *newHoverNodeItem = nullptr;
SkeletonGraphicsEdgeItem *newHoverEdgeItem = nullptr;
QList<QGraphicsItem *> items = scene()->items(mouseEventScenePos(event));
for (auto it = items.begin(); it != items.end(); it++) {
QGraphicsItem *item = *it;
if (item->data(0) == "node") {
newHoverNodeItem = (SkeletonGraphicsNodeItem *)item;
break;
} else if (item->data(0) == "edge") {
newHoverEdgeItem = (SkeletonGraphicsEdgeItem *)item;
}
}
if (newHoverNodeItem) {
newHoverEdgeItem = nullptr;
}
if (newHoverNodeItem != m_hoveredNodeItem) {
if (nullptr != m_hoveredNodeItem) {
m_hoveredNodeItem->setHovered(false);
}
m_hoveredNodeItem = newHoverNodeItem;
if (nullptr != m_hoveredNodeItem) {
m_hoveredNodeItem->setHovered(true);
}
}
if (newHoverEdgeItem != m_hoveredEdgeItem) {
if (nullptr != m_hoveredEdgeItem) {
m_hoveredEdgeItem->setHovered(false);
}
m_hoveredEdgeItem = newHoverEdgeItem;
if (nullptr != m_hoveredEdgeItem) {
m_hoveredEdgeItem->setHovered(true);
}
}
}
if (SkeletonDocumentEditMode::Add == m_document->editMode) {
QPointF mouseScenePos = mouseEventScenePos(event);
m_cursorNodeItem->setOrigin(mouseScenePos);
if (!m_cursorNodeItem->isVisible()) {
m_cursorNodeItem->show();
}
if (m_checkedNodeItem) {
m_cursorEdgeItem->setEndpoints(m_checkedNodeItem, m_cursorNodeItem);
if (!m_cursorEdgeItem->isVisible()) {
m_cursorEdgeItem->show();
}
}
return true;
}
if (SkeletonDocumentEditMode::Select == m_document->editMode) {
if (m_moveStarted && m_checkedNodeItem) {
QPointF mouseScenePos = mouseEventScenePos(event);
float byX = sceneRadiusToUnified(mouseScenePos.x() - m_lastScenePos.x());
float byY = sceneRadiusToUnified(mouseScenePos.y() - m_lastScenePos.y());
if (SkeletonProfile::Main == m_checkedNodeItem->profile()) {
emit moveNodeBy(m_checkedNodeItem->id(), byX, byY, 0);
} else {
emit moveNodeBy(m_checkedNodeItem->id(), 0, byY, byX);
}
m_lastScenePos = mouseScenePos;
return true;
}
}
return false;
}
bool SkeletonGraphicsWidget::wheel(QWheelEvent *event)
{
qreal delta = event->delta() / 10;
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
if (delta > 0)
delta = 1;
else
delta = -1;
} else {
if (fabs(delta) < 1)
delta = delta < 0 ? -1.0 : 1.0;
}
if (SkeletonDocumentEditMode::Add == m_document->editMode) {
if (m_cursorNodeItem->isVisible()) {
m_cursorNodeItem->setRadius(m_cursorNodeItem->radius() + delta);
return true;
}
} else if (SkeletonDocumentEditMode::Select == m_document->editMode) {
if (m_hoveredNodeItem) {
emit scaleNodeByAddRadius(m_hoveredNodeItem->id(), sceneRadiusToUnified(delta));
return true;
} else if (m_checkedNodeItem) {
emit scaleNodeByAddRadius(m_checkedNodeItem->id(), sceneRadiusToUnified(delta));
return true;
}
}
return false;
}
bool SkeletonGraphicsWidget::mouseRelease(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
bool processed = m_dragStarted || m_moveStarted;
if (m_dragStarted) {
m_dragStarted = false;
updateCursor();
}
if (m_moveStarted) {
m_moveStarted = false;
}
return processed;
}
return false;
}
QPointF SkeletonGraphicsWidget::mouseEventScenePos(QMouseEvent *event)
{
return mapToScene(mapFromGlobal(event->globalPos()));
}
bool SkeletonGraphicsWidget::mousePress(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
if (SkeletonDocumentEditMode::ZoomIn == m_document->editMode) {
ViewportAnchor lastAnchor = transformationAnchor();
setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ;
scale(1.5, 1.5);
setTransformationAnchor(lastAnchor);
return true;
} else if (SkeletonDocumentEditMode::ZoomOut == m_document->editMode) {
ViewportAnchor lastAnchor = transformationAnchor();
setTransformationAnchor(QGraphicsView::AnchorUnderMouse) ;
scale(0.5, 0.5);
setTransformationAnchor(lastAnchor);
if ((!verticalScrollBar() || !verticalScrollBar()->isVisible()) &&
(!horizontalScrollBar() || !horizontalScrollBar()->isVisible())) {
setTransform(QTransform());
}
return true;
} else if (SkeletonDocumentEditMode::Drag == m_document->editMode) {
if (!m_dragStarted) {
m_lastGlobalPos = event->globalPos();
m_dragStarted = true;
updateCursor();
}
} else if (SkeletonDocumentEditMode::Add == m_document->editMode) {
if (m_cursorNodeItem->isVisible()) {
if (m_checkedNodeItem) {
if (m_hoveredNodeItem && m_checkedNodeItem &&
m_hoveredNodeItem != m_checkedNodeItem &&
m_hoveredNodeItem->profile() == m_checkedNodeItem->profile()) {
if (m_document->findEdgeByNodes(m_checkedNodeItem->id(), m_hoveredNodeItem->id()))
return true;
emit addEdge(m_checkedNodeItem->id(), m_hoveredNodeItem->id());
return true;
}
}
QPointF mainProfile = m_cursorNodeItem->origin();
QPointF sideProfile = mainProfile;
if (mainProfile.x() >= scene()->width() / 2) {
sideProfile.setX(mainProfile.x() - scene()->width() / 4);
} else {
sideProfile.setX(mainProfile.x() +scene()->width() / 4);
}
QPointF unifiedMainPos = scenePosToUnified(mainProfile);
QPointF unifiedSidePos = scenePosToUnified(sideProfile);
if (isFloatEqual(m_lastAddedX, unifiedMainPos.x()) && isFloatEqual(m_lastAddedY, unifiedMainPos.y()) && isFloatEqual(m_lastAddedZ, unifiedSidePos.x()))
return true;
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_checkedNodeItem ? QUuid() : m_checkedNodeItem->id());
return true;
}
} else if (SkeletonDocumentEditMode::Select == m_document->editMode) {
bool processed = false;
if (m_hoveredNodeItem) {
if (m_checkedNodeItem != m_hoveredNodeItem) {
if (m_checkedNodeItem) {
emit uncheckNode(m_checkedNodeItem->id());
m_checkedNodeItem->setChecked(false);
}
m_checkedNodeItem = m_hoveredNodeItem;
m_checkedNodeItem->setChecked(true);
emit checkNode(m_checkedNodeItem->id());
}
m_moveStarted = true;
m_lastScenePos = mouseEventScenePos(event);
processed = true;
} else {
if (m_checkedNodeItem) {
m_checkedNodeItem->setChecked(false);
emit uncheckNode(m_checkedNodeItem->id());
m_checkedNodeItem = nullptr;
processed = true;
}
}
if (m_hoveredEdgeItem) {
if (m_checkedEdgeItem != m_hoveredEdgeItem) {
if (m_checkedEdgeItem) {
emit uncheckEdge(m_checkedEdgeItem->id());
m_checkedEdgeItem->setChecked(false);
}
m_checkedEdgeItem = m_hoveredEdgeItem;
m_checkedEdgeItem->setChecked(true);
emit checkEdge(m_checkedEdgeItem->id());
}
} else {
if (m_checkedEdgeItem) {
m_checkedEdgeItem->setChecked(false);
emit uncheckEdge(m_checkedEdgeItem->id());
m_checkedEdgeItem = nullptr;
processed = true;
}
}
if (processed) {
return true;
}
}
}
return false;
}
float SkeletonGraphicsWidget::sceneRadiusToUnified(float radius)
{
if (0 == scene()->height())
return 0;
return radius / scene()->height();
}
float SkeletonGraphicsWidget::sceneRadiusFromUnified(float radius)
{
return radius * scene()->height();
}
QPointF SkeletonGraphicsWidget::scenePosToUnified(QPointF pos)
{
if (0 == scene()->height())
return QPointF(0, 0);
return QPointF(pos.x() / scene()->height(), pos.y() / scene()->height());
}
QPointF SkeletonGraphicsWidget::scenePosFromUnified(QPointF pos)
{
return QPointF(pos.x() * scene()->height(), pos.y() * scene()->height());
}
bool SkeletonGraphicsWidget::mouseDoubleClick(QMouseEvent *event)
{
return false;
}
bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event)
{
if (event->key() == Qt::Key_Delete || event->key() ==Qt::Key_Backspace) {
bool processed = false;
if (m_checkedNodeItem) {
emit removeNode(m_checkedNodeItem->id());
processed = true;
}
if (m_checkedEdgeItem) {
emit removeEdge(m_checkedEdgeItem->id());
processed = true;
}
if (processed) {
return true;
}
} else if (event->key() == Qt::Key_A) {
if (SkeletonDocumentEditMode::Add == m_document->editMode) {
emit setEditMode(SkeletonDocumentEditMode::Select);
} else {
emit setEditMode(SkeletonDocumentEditMode::Add);
}
return true;
}
return false;
}
void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId)
{
const SkeletonNode *node = m_document->findNode(nodeId);
if (nullptr == node) {
qDebug() << "New node added but node id not exist:" << nodeId;
return;
}
SkeletonGraphicsNodeItem *mainProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Main);
SkeletonGraphicsNodeItem *sideProfileItem = new SkeletonGraphicsNodeItem(SkeletonProfile::Side);
mainProfileItem->setOrigin(scenePosFromUnified(QPointF(node->x, node->y)));
sideProfileItem->setOrigin(scenePosFromUnified(QPointF(node->z, node->y)));
mainProfileItem->setRadius(sceneRadiusFromUnified(node->radius));
sideProfileItem->setRadius(sceneRadiusFromUnified(node->radius));
mainProfileItem->setId(nodeId);
sideProfileItem->setId(nodeId);
scene()->addItem(mainProfileItem);
scene()->addItem(sideProfileItem);
nodeItemMap[nodeId] = std::make_pair(mainProfileItem, sideProfileItem);
if (nullptr == m_checkedNodeItem) {
m_checkedNodeItem = mainProfileItem;
} else {
if (SkeletonProfile::Main == m_checkedNodeItem->profile()) {
m_checkedNodeItem = mainProfileItem;
} else {
m_checkedNodeItem = sideProfileItem;
}
m_cursorEdgeItem->setEndpoints(m_checkedNodeItem, m_cursorNodeItem);
}
}
void SkeletonGraphicsWidget::edgeAdded(QUuid edgeId)
{
const SkeletonEdge *edge = m_document->findEdge(edgeId);
if (nullptr == edge) {
qDebug() << "New edge added but edge id not exist:" << edgeId;
return;
}
if (edge->nodeIds.size() != 2) {
qDebug() << "Invalid node count of edge:" << edgeId;
return;
}
QUuid fromNodeId = edge->nodeIds[0];
QUuid toNodeId = edge->nodeIds[1];
auto fromIt = nodeItemMap.find(fromNodeId);
if (fromIt == nodeItemMap.end()) {
qDebug() << "Node not found:" << fromNodeId;
return;
}
auto toIt = nodeItemMap.find(toNodeId);
if (toIt == nodeItemMap.end()) {
qDebug() << "Node not found:" << toNodeId;
return;
}
SkeletonGraphicsEdgeItem *mainProfileEdgeItem = new SkeletonGraphicsEdgeItem();
SkeletonGraphicsEdgeItem *sideProfileEdgeItem = new SkeletonGraphicsEdgeItem();
mainProfileEdgeItem->setId(edgeId);
sideProfileEdgeItem->setId(edgeId);
mainProfileEdgeItem->setEndpoints(fromIt->second.first, toIt->second.first);
sideProfileEdgeItem->setEndpoints(fromIt->second.second, toIt->second.second);
scene()->addItem(mainProfileEdgeItem);
scene()->addItem(sideProfileEdgeItem);
edgeItemMap[edgeId] = std::make_pair(mainProfileEdgeItem, sideProfileEdgeItem);
}
void SkeletonGraphicsWidget::nodeRemoved(QUuid nodeId)
{
m_lastAddedX = 0;
m_lastAddedY = 0;
m_lastAddedZ = 0;
auto nodeItemIt = nodeItemMap.find(nodeId);
if (nodeItemIt == nodeItemMap.end()) {
qDebug() << "Node removed but node id not exist:" << nodeId;
return;
}
if (m_hoveredNodeItem == nodeItemIt->second.first)
m_hoveredNodeItem = nullptr;
if (m_hoveredNodeItem == nodeItemIt->second.second)
m_hoveredNodeItem = nullptr;
if (m_checkedNodeItem == nodeItemIt->second.first)
m_checkedNodeItem = nullptr;
if (m_checkedNodeItem == nodeItemIt->second.second)
m_checkedNodeItem = nullptr;
delete nodeItemIt->second.first;
delete nodeItemIt->second.second;
nodeItemMap.erase(nodeItemIt);
}
void SkeletonGraphicsWidget::edgeRemoved(QUuid edgeId)
{
auto edgeItemIt = edgeItemMap.find(edgeId);
if (edgeItemIt == edgeItemMap.end()) {
qDebug() << "Edge removed but edge id not exist:" << edgeId;
return;
}
if (m_hoveredEdgeItem == edgeItemIt->second.first)
m_hoveredEdgeItem = nullptr;
if (m_hoveredEdgeItem == edgeItemIt->second.second)
m_hoveredEdgeItem = nullptr;
if (m_checkedEdgeItem == edgeItemIt->second.first)
m_checkedEdgeItem = nullptr;
if (m_checkedEdgeItem == edgeItemIt->second.second)
m_checkedEdgeItem = nullptr;
delete edgeItemIt->second.first;
delete edgeItemIt->second.second;
edgeItemMap.erase(edgeItemIt);
}
void SkeletonGraphicsWidget::nodeRadiusChanged(QUuid nodeId)
{
const SkeletonNode *node = m_document->findNode(nodeId);
if (nullptr == node) {
qDebug() << "Node changed but node id not exist:" << nodeId;
return;
}
auto it = nodeItemMap.find(nodeId);
if (it == nodeItemMap.end()) {
qDebug() << "Node not found:" << nodeId;
return;
}
float sceneRadius = sceneRadiusFromUnified(node->radius);
it->second.first->setRadius(sceneRadius);
it->second.second->setRadius(sceneRadius);
}
void SkeletonGraphicsWidget::nodeOriginChanged(QUuid nodeId)
{
const SkeletonNode *node = m_document->findNode(nodeId);
if (nullptr == node) {
qDebug() << "Node changed but node id not exist:" << nodeId;
return;
}
auto it = nodeItemMap.find(nodeId);
if (it == nodeItemMap.end()) {
qDebug() << "Node not found:" << nodeId;
return;
}
QPointF mainPos = scenePosFromUnified(QPointF(node->x, node->y));
QPointF sidePos = scenePosFromUnified(QPointF(node->z, node->y));
it->second.first->setOrigin(mainPos);
it->second.second->setOrigin(sidePos);
for (auto edgeIt = node->edgeIds.begin(); edgeIt != node->edgeIds.end(); edgeIt++) {
auto edgeItemIt = edgeItemMap.find(*edgeIt);
if (edgeItemIt == edgeItemMap.end()) {
qDebug() << "Edge item not found:" << *edgeIt;
continue;
}
edgeItemIt->second.first->updateAppearance();
edgeItemIt->second.second->updateAppearance();
}
}
void SkeletonGraphicsWidget::edgeChanged(QUuid edgeId)
{
}

View File

@ -0,0 +1,320 @@
#ifndef SKELETON_GRAPHICS_VIEW_H
#define SKELETON_GRAPHICS_VIEW_H
#include <map>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QGraphicsEllipseItem>
#include <QGraphicsLineItem>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QThread>
#include <cmath>
#include "skeletondocument.h"
#include "turnaroundloader.h"
#include "theme.h"
class SkeletonGraphicsNodeItem : public QGraphicsEllipseItem
{
public:
SkeletonGraphicsNodeItem(SkeletonProfile profile=SkeletonProfile::Unknown) :
m_profile(profile),
m_hovered(false),
m_checked(false)
{
setData(0, "node");
setRadius(32);
}
void updateAppearance()
{
QColor color = Theme::white;
switch (m_profile)
{
case SkeletonProfile::Unknown:
break;
case SkeletonProfile::Main:
color = Theme::red;
break;
case SkeletonProfile::Side:
color = Theme::green;
break;
}
QColor penColor = color;
penColor.setAlphaF(m_checked ? Theme::checkedAlpha : Theme::normalAlpha);
QPen pen(penColor);
pen.setWidth(0);
setPen(pen);
QColor brushColor = color;
brushColor.setAlphaF((m_checked || m_hovered) ? Theme::fillAlpha : 0);
QBrush brush(brushColor);
setBrush(brush);
}
void setOrigin(QPointF point)
{
QPointF moveBy = point - origin();
QRectF newRect = rect();
newRect.adjust(moveBy.x(), moveBy.y(), moveBy.x(), moveBy.y());
setRect(newRect);
updateAppearance();
}
QPointF origin()
{
return QPointF(rect().x() + rect().width() / 2,
rect().y() + rect().height() / 2);
}
float radius()
{
return rect().width() / 2;
}
void setRadius(float radius)
{
if (radius < 4)
radius = 4;
QPointF oldOrigin = origin();
setRect(oldOrigin.x() - radius, oldOrigin.y() - radius,
radius * 2, radius * 2);
updateAppearance();
}
SkeletonProfile profile()
{
return m_profile;
}
QUuid id()
{
return m_uuid;
}
void setId(QUuid id)
{
m_uuid = id;
}
void setHovered(bool hovered)
{
m_hovered = hovered;
updateAppearance();
}
void setChecked(bool checked)
{
m_checked = checked;
updateAppearance();
}
bool checked()
{
return m_checked;
}
private:
QUuid m_uuid;
SkeletonProfile m_profile;
bool m_hovered;
bool m_checked;
};
class SkeletonGraphicsEdgeItem : public QGraphicsPolygonItem
{
public:
SkeletonGraphicsEdgeItem() :
m_firstItem(nullptr),
m_secondItem(nullptr),
m_hovered(false),
m_checked(false)
{
setData(0, "edge");
}
void setEndpoints(SkeletonGraphicsNodeItem *first, SkeletonGraphicsNodeItem *second)
{
m_firstItem = first;
m_secondItem = second;
updateAppearance();
}
void updateAppearance()
{
if (nullptr == m_firstItem || nullptr == m_secondItem)
return;
QLineF line(m_firstItem->origin(), m_secondItem->origin());
QPolygonF polygon;
float radAngle = line.angle() * M_PI / 180;
float dx = 5 * sin(radAngle);
float dy = 5 * cos(radAngle);
QPointF offset1 = QPointF(dx, dy);
QPointF offset2 = QPointF(-dx, -dy);
polygon << line.p1() + offset1 << line.p1() + offset2 << line.p2() + offset2 << line.p2() + offset1;
setPolygon(polygon);
QColor color = Theme::white;
switch (m_firstItem->profile())
{
case SkeletonProfile::Unknown:
break;
case SkeletonProfile::Main:
color = Theme::red;
break;
case SkeletonProfile::Side:
color = Theme::green;
break;
}
QColor penColor = color;
penColor.setAlphaF((m_checked || m_hovered) ? Theme::checkedAlpha : Theme::normalAlpha);
QPen pen(penColor);
pen.setWidth(0);
setPen(pen);
}
QUuid id()
{
return m_uuid;
}
void setId(QUuid id)
{
m_uuid = id;
}
void setHovered(bool hovered)
{
m_hovered = hovered;
updateAppearance();
}
void setChecked(bool checked)
{
m_checked = checked;
updateAppearance();
}
private:
QUuid m_uuid;
SkeletonGraphicsNodeItem *m_firstItem;
SkeletonGraphicsNodeItem *m_secondItem;
QPolygonF m_selectionPolygon;
bool m_hovered;
bool m_checked;
};
class SkeletonGraphicsFunctions
{
public:
virtual bool mouseMove(QMouseEvent *event) = 0;
virtual bool wheel(QWheelEvent *event) = 0;
virtual bool mouseRelease(QMouseEvent *event) = 0;
virtual bool mousePress(QMouseEvent *event) = 0;
virtual bool mouseDoubleClick(QMouseEvent *event) = 0;
virtual bool keyPress(QKeyEvent *event) = 0;
};
class SkeletonGraphicsWidget : public QGraphicsView, public SkeletonGraphicsFunctions
{
Q_OBJECT
signals:
void addNode(float x, float y, float z, float radius, QUuid fromNodeId);
void uncheckNode(QUuid nodeId);
void checkNode(QUuid nodeId);
void uncheckEdge(QUuid edgeId);
void checkEdge(QUuid edgeId);
void scaleNodeByAddRadius(QUuid nodeId, float amount);
void moveNodeBy(QUuid nodeId, float x, float y, float z);
void removeNode(QUuid nodeId);
void setEditMode(SkeletonDocumentEditMode mode);
void removeEdge(QUuid edgeId);
void addEdge(QUuid fromNodeId, QUuid toNodeId);
void cursorChanged();
public:
SkeletonGraphicsWidget(const SkeletonDocument *document);
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
std::map<QUuid, std::pair<SkeletonGraphicsEdgeItem *, SkeletonGraphicsEdgeItem *>> edgeItemMap;
bool mouseMove(QMouseEvent *event);
bool wheel(QWheelEvent *event);
bool mouseRelease(QMouseEvent *event);
bool mousePress(QMouseEvent *event);
bool mouseDoubleClick(QMouseEvent *event);
bool keyPress(QKeyEvent *event);
protected:
void mouseMoveEvent(QMouseEvent *event);
void wheelEvent(QWheelEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseDoubleClickEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
public slots:
void nodeAdded(QUuid nodeId);
void edgeAdded(QUuid edgeId);
void nodeRemoved(QUuid nodeId);
void edgeRemoved(QUuid edgeId);
void nodeRadiusChanged(QUuid nodeId);
void nodeOriginChanged(QUuid nodeId);
void edgeChanged(QUuid edgeId);
void turnaroundChanged();
void canvasResized();
void editModeChanged();
void updateCursor();
private slots:
void turnaroundImageReady();
private:
QPointF mouseEventScenePos(QMouseEvent *event);
QPointF scenePosToUnified(QPointF pos);
QPointF scenePosFromUnified(QPointF pos);
float sceneRadiusToUnified(float radius);
float sceneRadiusFromUnified(float radius);
void updateTurnaround();
void updateItems();
private:
QGraphicsPixmapItem *m_backgroundItem;
const SkeletonDocument *m_document;
bool m_turnaroundChanged;
TurnaroundLoader *m_turnaroundLoader;
bool m_dragStarted;
QPoint m_lastGlobalPos;
bool m_moveStarted;
QPointF m_lastScenePos;
SkeletonGraphicsNodeItem *m_cursorNodeItem;
SkeletonGraphicsEdgeItem *m_cursorEdgeItem;
SkeletonGraphicsNodeItem *m_checkedNodeItem;
SkeletonGraphicsNodeItem *m_hoveredNodeItem;
SkeletonGraphicsEdgeItem *m_hoveredEdgeItem;
SkeletonGraphicsEdgeItem *m_checkedEdgeItem;
float m_lastAddedX;
float m_lastAddedY;
float m_lastAddedZ;
};
class SkeletonGraphicsContainerWidget : public QWidget
{
Q_OBJECT
signals:
void containerSizeChanged(QSize size);
public:
SkeletonGraphicsContainerWidget() :
m_graphicsWidget(nullptr)
{
}
void resizeEvent(QResizeEvent *event) override
{
if (m_graphicsWidget && m_graphicsWidget->size() != event->size())
emit containerSizeChanged(event->size());
}
void setGraphicsWidget(SkeletonGraphicsWidget *graphicsWidget)
{
m_graphicsWidget = graphicsWidget;
}
private:
SkeletonGraphicsWidget *m_graphicsWidget;
};
#endif

View File

View File

View File

@ -0,0 +1,50 @@
#include <QFormLayout>
#include "skeletonnodepropertywidget.h"
SkeletonNodePropertyWidget::SkeletonNodePropertyWidget(const SkeletonDocument *document) :
m_rootMarkModeComboBox(nullptr),
m_document(document)
{
m_rootMarkModeComboBox = new QComboBox;
m_rootMarkModeComboBox->insertItem(0, tr("Auto"), (int)SkeletonNodeRootMarkMode::Auto);
m_rootMarkModeComboBox->insertItem(1, tr("Mark as Root"), (int)SkeletonNodeRootMarkMode::MarkAsRoot);
m_rootMarkModeComboBox->insertItem(2, tr("Mark as Not Root"), (int)SkeletonNodeRootMarkMode::MarkAsNotRoot);
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(tr("Root Mark:"), m_rootMarkModeComboBox);
setLayout(formLayout);
hide();
connect(m_rootMarkModeComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), [=](int index) {
if (-1 != index) {
int mode = m_rootMarkModeComboBox->itemData(index).toInt();
emit setNodeRootMarkMode(m_nodeId, static_cast<SkeletonNodeRootMarkMode>(mode));
}
});
}
void SkeletonNodePropertyWidget::showProperties(QUuid nodeId)
{
m_nodeId = nodeId;
updateData();
show();
}
void SkeletonNodePropertyWidget::updateData()
{
const SkeletonNode *node = m_document->findNode(m_nodeId);
if (nullptr == node) {
hide();
return;
}
int selectIndex = m_rootMarkModeComboBox->findData((int)node->rootMarkMode);
m_rootMarkModeComboBox->setCurrentIndex(selectIndex);
}
QUuid SkeletonNodePropertyWidget::currentNodeId()
{
return m_nodeId;
}

View File

@ -0,0 +1,26 @@
#ifndef SKELETON_NODE_PROPERTY_WIDGET_H
#define SKELETON_NODE_PROPERTY_WIDGET_H
#include <QWidget>
#include <QDoubleSpinBox>
#include <QComboBox>
#include "skeletondocument.h"
class SkeletonNodePropertyWidget : public QWidget
{
Q_OBJECT
signals:
void setNodeRootMarkMode(QUuid nodeId, SkeletonNodeRootMarkMode mode);
public slots:
void showProperties(QUuid nodeId);
public:
SkeletonNodePropertyWidget(const SkeletonDocument *document);
QUuid currentNodeId();
private:
void updateData();
private:
QComboBox *m_rootMarkModeComboBox;
const SkeletonDocument *m_document;
QUuid m_nodeId;
};
#endif

View File

@ -0,0 +1,89 @@
#include <QGridLayout>
#include <QDebug>
#include "skeletonpartlistwidget.h"
#include "theme.h"
SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid partId) :
m_document(document),
m_partId(partId)
{
m_visibleButton = new QPushButton(QChar(fa::eye));
//m_visibleButton->setStyleSheet("QPushButton {border: none; background: none;}");
m_visibleButton->setFont(Theme::awesome()->font(Theme::toolIconFontSize * 2 / 3));
m_visibleButton->setFixedSize(Theme::toolIconSize * 2 / 3, Theme::toolIconSize * 2 / 3);
m_previewLabel = new QLabel;
//m_nameLabel = new QLabel;
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(m_visibleButton, 0, 0);
mainLayout->addWidget(m_previewLabel, 0, 1);
//mainLayout->addWidget(m_nameLabel, 0, 2);
setLayout(mainLayout);
}
QLabel *SkeletonPartWidget::previewLabel()
{
return m_previewLabel;
}
void SkeletonPartWidget::reload()
{
const SkeletonPart *part = m_document->findPart(m_partId);
if (!part) {
qDebug() << "Part not found:" << m_partId;
return;
}
//m_nameLabel->setText(part->name.isEmpty() ? part->id.toString() : part->name);
m_previewLabel->setPixmap(QPixmap::fromImage(part->preview));
}
SkeletonPartListWidget::SkeletonPartListWidget(const SkeletonDocument *document) :
m_document(document)
{
setSelectionMode(QAbstractItemView::NoSelection);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
void SkeletonPartListWidget::partChanged(QUuid partId)
{
auto itemIt = m_itemMap.find(partId);
if (itemIt == m_itemMap.end()) {
return;
}
}
void SkeletonPartListWidget::partListChanged()
{
clear();
m_itemMap.clear();
for (auto partIdIt = m_document->partIds.begin(); partIdIt != m_document->partIds.end(); partIdIt++) {
QUuid partId = *partIdIt;
QListWidgetItem *item = new QListWidgetItem(this);
item->setSizeHint(QSize(width(), Theme::previewImageSize));
addItem(item);
SkeletonPartWidget *widget = new SkeletonPartWidget(m_document, partId);
setItemWidget(item, widget);
widget->reload();
m_itemMap[partId] = item;
}
}
void SkeletonPartListWidget::partPreviewChanged(QUuid partid)
{
const SkeletonPart *part = m_document->findPart(partid);
if (!part) {
qDebug() << "Part not found:" << partid;
return;
}
auto item = m_itemMap.find(partid);
if (item == m_itemMap.end()) {
qDebug() << "Part item not found:" << partid;
return;
}
SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second);
widget->previewLabel()->setPixmap(QPixmap::fromImage(part->preview));
}

View File

@ -0,0 +1,38 @@
#ifndef SKELETON_PART_LIST_WIDGET_H
#define SKELETON_PART_LIST_WIDGET_H
#include <QListWidget>
#include <map>
#include <QLabel>
#include <QPushButton>
#include "skeletondocument.h"
class SkeletonPartWidget : public QWidget
{
Q_OBJECT
public:
SkeletonPartWidget(const SkeletonDocument *document, QUuid partId);
void reload();
QLabel *previewLabel();
private:
const SkeletonDocument *m_document;
QUuid m_partId;
QLabel *m_previewLabel;
QPushButton *m_visibleButton;
QLabel *m_nameLabel;
};
class SkeletonPartListWidget : public QListWidget
{
Q_OBJECT
public:
SkeletonPartListWidget(const SkeletonDocument *document);
public slots:
void partChanged(QUuid partId);
void partListChanged();
void partPreviewChanged(QUuid partid);
private:
const SkeletonDocument *m_document;
std::map<QUuid, QListWidgetItem *> m_itemMap;
};
#endif

View File

@ -12,6 +12,8 @@ public:
std::map<QString, QString> canvas; std::map<QString, QString> canvas;
std::map<QString, std::map<QString, QString>> nodes; std::map<QString, std::map<QString, QString>> nodes;
std::map<QString, std::map<QString, QString>> edges; std::map<QString, std::map<QString, QString>> edges;
std::map<QString, std::map<QString, QString>> parts;
std::vector<QString> partIdList;
public: public:
SkeletonSnapshot(); SkeletonSnapshot();
void splitByConnectivity(std::vector<SkeletonSnapshot> *groups); void splitByConnectivity(std::vector<SkeletonSnapshot> *groups);

View File

@ -1,135 +0,0 @@
#include "skeletontomesh.h"
#include "meshlite.h"
#include "skeletoneditnodeitem.h"
#include "skeletoneditedgeitem.h"
#include "skeletonsnapshot.h"
#include "unionmesh.h"
#include <vector>
struct NodeItemInfo
{
int index;
int neighborCount;
};
SkeletonToMesh::SkeletonToMesh(SkeletonSnapshot *snapshot) :
m_mesh(NULL),
m_snapshot(*snapshot)
{
}
SkeletonToMesh::~SkeletonToMesh()
{
delete m_mesh;
}
Mesh *SkeletonToMesh::takeResultMesh()
{
Mesh *mesh = m_mesh;
m_mesh = NULL;
return mesh;
}
#if USE_CARVE == 1
#define ExternalMesh carve::poly::Polyhedron
#define makeExternalMeshFromMeshlite makeCarveMeshFromMeshlite
#define unionExternalMeshs unionCarveMeshs
#define makeMeshliteMeshFromExternal makeMeshliteMeshFromCarve
#endif
#if USE_CGAL == 1
#define ExternalMesh CgalMesh
#define makeExternalMeshFromMeshlite makeCgalMeshFromMeshlite
#define unionExternalMeshs unionCgalMeshs
#define makeMeshliteMeshFromExternal makeMeshliteMeshFromCgal
#endif
void SkeletonToMesh::process()
{
std::vector<SkeletonSnapshot> groups;
QRectF globalFront = m_snapshot.boundingBoxFront();
QRectF globalSide = m_snapshot.boundingBoxSide();
QString rootNodeId = m_snapshot.rootNode();
printf("rootNodeId:%s\n", rootNodeId.toUtf8().constData());
float frontMiddleX = m_snapshot.nodes[rootNodeId]["x"].toFloat();
float frontMiddleY = m_snapshot.nodes[rootNodeId]["y"].toFloat();
float sideMiddleX = m_snapshot.nodes[m_snapshot.nodes[rootNodeId]["nextSidePair"]]["x"].toFloat();
bool combineEnabled = "true" == m_snapshot.canvas["combine"];
bool unionEnabled = "true" == m_snapshot.canvas["union"];
bool subdivEnabled = "true" == m_snapshot.canvas["subdiv"];
m_snapshot.splitByConnectivity(&groups);
std::vector<int> meshIds;
void *meshliteContext = meshlite_create_context();
for (size_t i = 0; i < groups.size(); i++) {
SkeletonSnapshot *skeleton = &groups[i];
//QRectF front = skeleton->boundingBoxFront();
//QRectF side = skeleton->boundingBoxSide();
//float canvasWidth = skeleton->canvas["width"].toFloat();
//float canvasHeight = skeleton->canvas["height"].toFloat();
float canvasHeight = globalFront.height();
std::map<QString, int> bmeshNodeMap;
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
int bmeshId = meshlite_bmesh_create(meshliteContext);
for (nodeIterator = skeleton->nodes.begin(); nodeIterator != skeleton->nodes.end(); nodeIterator++) {
if ("red" != nodeIterator->second["sideColorName"])
continue;
std::map<QString, std::map<QString, QString>>::iterator nextSidePair = skeleton->nodes.find(nodeIterator->second["nextSidePair"]);
if (nextSidePair == skeleton->nodes.end())
continue;
float x = (nodeIterator->second["x"].toFloat() - frontMiddleX) / canvasHeight;
float y = (nodeIterator->second["y"].toFloat() - frontMiddleY) / canvasHeight;
float z = (nextSidePair->second["x"].toFloat() - sideMiddleX) / canvasHeight;
float r = nodeIterator->second["radius"].toFloat() / canvasHeight;
int bmeshNodeId = meshlite_bmesh_add_node(meshliteContext, bmeshId, x, y, z, r);
printf("meshlite_bmesh_add_node x:%f y:%f z:%f r:%f nodeName:%s bmeshNodeId:%d\n", x, y, z, r, nodeIterator->first.toUtf8().constData(), bmeshNodeId);
bmeshNodeMap[nodeIterator->first] = bmeshNodeId;
}
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
for (edgeIterator = skeleton->edges.begin(); edgeIterator != skeleton->edges.end(); edgeIterator++) {
std::map<QString, int>::iterator from = bmeshNodeMap.find(edgeIterator->second["from"]);
if (from == bmeshNodeMap.end())
continue;
std::map<QString, int>::iterator to = bmeshNodeMap.find(edgeIterator->second["to"]);
if (to == bmeshNodeMap.end())
continue;
printf("meshlite_bmesh_add_edge %d -> %d\n", from->second, to->second);
meshlite_bmesh_add_edge(meshliteContext, bmeshId, from->second, to->second);
}
if (bmeshNodeMap.size() > 0 && !skeleton->rootNode().isEmpty()) {
int meshId = meshlite_bmesh_generate_mesh(meshliteContext, bmeshId, bmeshNodeMap[skeleton->rootNode()]);
meshIds.push_back(meshId);
}
meshlite_bmesh_destroy(meshliteContext, bmeshId);
}
if (meshIds.size() > 0) {
int mergedMeshId = 0;
if (unionEnabled) {
mergedMeshId = unionMeshs(meshliteContext, meshIds);
} else {
mergedMeshId = mergeMeshs(meshliteContext, meshIds);
}
if (combineEnabled) {
if (mergedMeshId > 0) {
mergedMeshId = meshlite_combine_coplanar_faces(meshliteContext, mergedMeshId);
}
}
if (subdivEnabled) {
if (mergedMeshId > 0 && meshlite_is_triangulated_manifold(meshliteContext, mergedMeshId)) {
mergedMeshId = meshlite_subdivide(meshliteContext, mergedMeshId);
}
}
if (mergedMeshId > 0) {
m_mesh = new Mesh(meshliteContext, mergedMeshId);
}
}
meshlite_destroy_context(meshliteContext);
emit finished();
}

View File

@ -1,27 +0,0 @@
#ifndef SKELETON_TO_MESH_H
#define SKELETON_TO_MESH_H
#include <QObject>
#include <QList>
#include <vector>
#include <map>
#include "skeletoneditgraphicsview.h"
#include "mesh.h"
#include "skeletonsnapshot.h"
class SkeletonToMesh : public QObject
{
Q_OBJECT
public:
SkeletonToMesh(SkeletonSnapshot *snapshot);
~SkeletonToMesh();
Mesh *takeResultMesh();
signals:
void finished();
public slots:
void process();
private:
Mesh *m_mesh;
SkeletonSnapshot m_snapshot;
};
#endif

View File

@ -1,214 +0,0 @@
#include <QVBoxLayout>
#include <QPushButton>
#include <QButtonGroup>
#include <QGridLayout>
#include <QToolBar>
#include <QThread>
#include <QFileDialog>
#include <QApplication>
#include <QDesktopWidget>
#include <assert.h>
#include "skeletonwidget.h"
#include "meshlite.h"
#include "skeletontomesh.h"
#include "turnaroundloader.h"
SkeletonWidget::SkeletonWidget(QWidget *parent) :
QWidget(parent),
m_skeletonToMesh(NULL),
m_skeletonDirty(false),
m_turnaroundLoader(NULL),
m_turnaroundDirty(false)
{
QHBoxLayout *topLayout = new QHBoxLayout;
topLayout->addStretch();
topLayout->setContentsMargins(0, 0, 0, 0);
m_graphicsView = new SkeletonEditGraphicsView(this);
m_graphicsView->setRenderHint(QPainter::Antialiasing, false);
m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_graphicsView->setBackgroundBrush(QBrush(QWidget::palette().color(QWidget::backgroundRole()), Qt::SolidPattern));
m_graphicsView->setContentsMargins(0, 0, 0, 0);
m_graphicsView->setFrameStyle(QFrame::NoFrame);
/*
m_modelWidget = new ModelWidget(this);
m_modelWidget->setMinimumSize(128, 128);
m_modelWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_modelWidget->setWindowFlags(Qt::Tool | Qt::Window);
m_modelWidget->setWindowTitle("3D Model");
*/
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addSpacing(0);
rightLayout->addStretch();
rightLayout->setContentsMargins(0, 0, 0, 0);
QToolBar *toolbar = new QToolBar;
toolbar->setIconSize(QSize(18, 18));
toolbar->setOrientation(Qt::Vertical);
QAction *addAction = new QAction(tr("Add"), this);
addAction->setIcon(QPixmap(":/resources/add.svg"));
toolbar->addAction(addAction);
QAction *selectAction = new QAction(tr("Select"), this);
selectAction->setIcon(QPixmap(":/resources/rotate.svg"));
toolbar->addAction(selectAction);
QAction *rangeSelectAction = new QAction(tr("Range Select"), this);
rangeSelectAction->setIcon(QPixmap(":/resources/zoomin.svg"));
toolbar->addAction(rangeSelectAction);
toolbar->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *leftLayout = new QVBoxLayout;
leftLayout->addWidget(toolbar);
leftLayout->addStretch();
leftLayout->addSpacing(0);
leftLayout->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *middleLayout = new QHBoxLayout;
//middleLayout->addLayout(leftLayout);
middleLayout->addWidget(m_graphicsView);
//middleLayout->addLayout(rightLayout);
middleLayout->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *mainLayout = new QVBoxLayout;
//mainLayout->addLayout(topLayout);
//mainLayout->addSpacing(10);
mainLayout->addLayout(middleLayout);
mainLayout->setContentsMargins(0, 0, 0, 0);
setLayout(mainLayout);
setContentsMargins(0, 0, 0, 0);
setWindowTitle(tr("Dust 3D"));
bool connectResult;
connectResult = connect(addAction, SIGNAL(triggered(bool)), m_graphicsView, SLOT(turnOnAddNodeMode()));
assert(connectResult);
connectResult = connectResult = connect(selectAction, SIGNAL(triggered(bool)), m_graphicsView, SLOT(turnOffAddNodeMode()));
assert(connectResult);
connectResult = connect(m_graphicsView, SIGNAL(nodesChanged()), this, SLOT(skeletonChanged()));
assert(connectResult);
connectResult = connect(m_graphicsView, SIGNAL(sizeChanged()), this, SLOT(turnaroundChanged()));
assert(connectResult);
connectResult = connect(m_graphicsView, SIGNAL(changeTurnaroundTriggered()), this, SLOT(changeTurnaround()));
assert(connectResult);
//connectResult = connect(clipButton, SIGNAL(clicked()), this, SLOT(saveClip()));
//assert(connectResult);
}
SkeletonEditGraphicsView *SkeletonWidget::graphicsView()
{
return m_graphicsView;
}
ModelWidget *SkeletonWidget::modelWidget()
{
//return m_modelWidget;
return graphicsView()->modelWidget();
}
void SkeletonWidget::showModelingWidgetAtCorner()
{
/*
if (!m_modelWidget->isVisible()) {
QPoint pos = QPoint(QApplication::desktop()->width(),
QApplication::desktop()->height());
m_modelWidget->move(pos.x() - m_modelWidget->width(),
pos.y() - m_modelWidget->height());
m_modelWidget->show();
}*/
}
void SkeletonWidget::meshReady()
{
Mesh *resultMesh = m_skeletonToMesh->takeResultMesh();
//showModelingWidgetAtCorner();
//m_modelWidget->updateMesh(resultMesh);
modelWidget()->updateMesh(resultMesh);
delete m_skeletonToMesh;
m_skeletonToMesh = NULL;
if (m_skeletonDirty) {
skeletonChanged();
}
}
void SkeletonWidget::skeletonChanged()
{
if (m_skeletonToMesh) {
m_skeletonDirty = true;
return;
}
m_skeletonDirty = false;
QThread *thread = new QThread;
SkeletonSnapshot snapshot;
m_graphicsView->saveToSnapshot(&snapshot);
m_skeletonToMesh = new SkeletonToMesh(&snapshot);
m_skeletonToMesh->moveToThread(thread);
connect(thread, SIGNAL(started()), m_skeletonToMesh, SLOT(process()));
connect(m_skeletonToMesh, SIGNAL(finished()), this, SLOT(meshReady()));
connect(m_skeletonToMesh, SIGNAL(finished()), thread, SLOT(quit()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
void SkeletonWidget::turnaroundChanged()
{
if (m_turnaroundFilename.isEmpty())
return;
if (m_turnaroundLoader) {
m_turnaroundDirty = true;
return;
}
m_turnaroundDirty = false;
QThread *thread = new QThread;
m_turnaroundLoader = new TurnaroundLoader(m_turnaroundFilename,
m_graphicsView->rect().size());
m_turnaroundLoader->moveToThread(thread);
connect(thread, SIGNAL(started()), m_turnaroundLoader, SLOT(process()));
connect(m_turnaroundLoader, SIGNAL(finished()), this, SLOT(turnaroundImageReady()));
connect(m_turnaroundLoader, SIGNAL(finished()), thread, SLOT(quit()));
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
thread->start();
}
void SkeletonWidget::turnaroundImageReady()
{
QImage *backgroundImage = m_turnaroundLoader->takeResultImage();
if (backgroundImage && backgroundImage->width() > 0 && backgroundImage->height() > 0) {
m_graphicsView->updateBackgroundImage(*backgroundImage);
}
delete backgroundImage;
delete m_turnaroundLoader;
m_turnaroundLoader = NULL;
if (m_turnaroundDirty) {
turnaroundChanged();
}
}
void SkeletonWidget::changeTurnaround()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"),
QString(),
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
if (fileName.isEmpty())
return;
m_turnaroundFilename = fileName;
turnaroundChanged();
}

View File

@ -1,36 +0,0 @@
#ifndef SKELETON_WIDGET_H
#define SKELETON_WIDGET_H
#include <QWidget>
#include <QString>
#include <QVBoxLayout>
#include <QPushButton>
#include "modelwidget.h"
#include "skeletontomesh.h"
#include "turnaroundloader.h"
#include "skeletoneditgraphicsview.h"
class SkeletonWidget : public QWidget
{
Q_OBJECT
public:
SkeletonWidget(QWidget *parent=0);
SkeletonEditGraphicsView *graphicsView();
ModelWidget *modelWidget();
public slots:
void skeletonChanged();
void meshReady();
void turnaroundChanged();
void turnaroundImageReady();
void changeTurnaround();
void showModelingWidgetAtCorner();
private:
ModelWidget *m_modelWidget;
SkeletonEditGraphicsView *m_graphicsView;
SkeletonToMesh *m_skeletonToMesh;
bool m_skeletonDirty;
TurnaroundLoader *m_turnaroundLoader;
bool m_turnaroundDirty;
QString m_turnaroundFilename;
};
#endif

View File

@ -25,6 +25,23 @@ float Theme::edgeAlpha = 1.0;
float Theme::fillAlpha = 50.0 / 255; float Theme::fillAlpha = 50.0 / 255;
int Theme::skeletonNodeBorderSize = 0; int Theme::skeletonNodeBorderSize = 0;
int Theme::skeletonEdgeWidth = 0; int Theme::skeletonEdgeWidth = 0;
int Theme::toolIconFontSize = 16;
int Theme::toolIconSize = 24;
int Theme::previewImageSize = 64;
QtAwesome *Theme::awesome()
{
static QtAwesome *s_awesome = nullptr;
if (nullptr == s_awesome) {
s_awesome = new QtAwesome();
s_awesome->initFontAwesome();
s_awesome->setDefaultOption("color", Theme::white);
s_awesome->setDefaultOption("color-disabled", QColor(0xcc, 0xcc, 0xcc));
s_awesome->setDefaultOption("color-active", Theme::white);
s_awesome->setDefaultOption("color-selected", Theme::white);
}
return s_awesome;
}
std::map<QString, QString> createSideColorNameMap() { std::map<QString, QString> createSideColorNameMap() {
std::map<QString, QString> map; std::map<QString, QString> map;

View File

@ -3,6 +3,7 @@
#include <QColor> #include <QColor>
#include <QString> #include <QString>
#include <map> #include <map>
#include "QtAwesome.h"
class Theme class Theme
{ {
@ -22,6 +23,10 @@ public:
static QString tabButtonStylesheet; static QString tabButtonStylesheet;
static std::map<QString, QString> nextSideColorNameMap; static std::map<QString, QString> nextSideColorNameMap;
static std::map<QString, QColor> sideColorNameToColorMap; static std::map<QString, QColor> sideColorNameToColorMap;
static QtAwesome *awesome();
static int toolIconFontSize;
static int toolIconSize;
static int previewImageSize;
}; };
#endif #endif

View File

@ -1,12 +1,18 @@
#include "turnaroundloader.h" #include "turnaroundloader.h"
TurnaroundLoader::TurnaroundLoader(const QString &filename, QSize viewSize) : TurnaroundLoader::TurnaroundLoader(const QString &filename, QSize viewSize) :
m_resultImage(NULL) m_resultImage(nullptr)
{ {
m_filename = filename; m_filename = filename;
m_viewSize = viewSize; m_viewSize = viewSize;
} }
TurnaroundLoader::TurnaroundLoader(const QImage &image, QSize viewSize)
{
m_inputImage = image;
m_viewSize = viewSize;
}
TurnaroundLoader::~TurnaroundLoader() TurnaroundLoader::~TurnaroundLoader()
{ {
delete m_resultImage; delete m_resultImage;
@ -15,13 +21,17 @@ TurnaroundLoader::~TurnaroundLoader()
QImage *TurnaroundLoader::takeResultImage() QImage *TurnaroundLoader::takeResultImage()
{ {
QImage *returnImage = m_resultImage; QImage *returnImage = m_resultImage;
m_resultImage = NULL; m_resultImage = nullptr;
return returnImage; return returnImage;
} }
void TurnaroundLoader::process() void TurnaroundLoader::process()
{ {
QImage image(m_filename); if (m_inputImage.isNull()) {
m_resultImage = new QImage(image.scaled(m_viewSize, Qt::KeepAspectRatio)); QImage image(m_filename);
m_resultImage = new QImage(image.scaled(m_viewSize, Qt::KeepAspectRatio));
} else {
m_resultImage = new QImage(m_inputImage.scaled(m_viewSize, Qt::KeepAspectRatio));
}
emit finished(); emit finished();
} }

View File

@ -10,6 +10,7 @@ class TurnaroundLoader : public QObject
Q_OBJECT Q_OBJECT
public: public:
TurnaroundLoader(const QString &filename, QSize viewSize); TurnaroundLoader(const QString &filename, QSize viewSize);
TurnaroundLoader(const QImage &image, QSize viewSize);
~TurnaroundLoader(); ~TurnaroundLoader();
QImage *takeResultImage(); QImage *takeResultImage();
signals: signals:
@ -18,6 +19,7 @@ public slots:
void process(); void process();
private: private:
QImage *m_resultImage; QImage *m_resultImage;
QImage m_inputImage;
QString m_filename; QString m_filename;
QSize m_viewSize; QSize m_viewSize;
}; };

20
src/util.cpp Normal file
View File

@ -0,0 +1,20 @@
#include <cmath>
#include "util.h"
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key)
{
auto it = map.find(key);
if (it == map.end())
return QString();
return it->second;
}
bool isTrueValueString(const QString &str)
{
return "true" == str || "True" == str || "1" == str;
}
bool isFloatEqual(float a, float b)
{
return fabs(a - b) <= 0.000001;
}

10
src/util.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef UTIL_H
#define UTIL_H
#include <QString>
#include <map>
QString valueOfKeyInMapOrEmpty(const std::map<QString, QString> &map, const QString &key);
bool isTrueValueString(const QString &str);
bool isFloatEqual(float a, float b);
#endif

29
thirdparty/QtAwesome/LICENSE.md vendored Executable file
View File

@ -0,0 +1,29 @@
MIT License
===========
Copyright 2013-2015 [Reliable Bits Software by Blommers IT](http://blommersit.nl). All Rights Reserved.
Author [Rick Blommers](mailto:rick@blommersit.nl)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Font Awesome License
====================
[https://github.com/FortAwesome/Font-Awesome](https://github.com/FortAwesome/Font-Awesome)
The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL)
The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/)
"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"

1146
thirdparty/QtAwesome/QtAwesome/QtAwesome.cpp vendored Executable file

File diff suppressed because it is too large Load Diff

876
thirdparty/QtAwesome/QtAwesome/QtAwesome.h vendored Executable file
View File

@ -0,0 +1,876 @@
/**
* QtAwesome - use font-awesome (or other font icons) in your c++ / Qt Application
*
* MIT Licensed
*
* Copyright 2013-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved.
* Author Rick Blommers
*/
#ifndef QTAWESOME_H
#define QTAWESOME_H
#include "QtAwesomeAnim.h"
#include <QIcon>
#include <QIconEngine>
#include <QPainter>
#include <QRect>
#include <QVariantMap>
/// A list of all icon-names with the codepoint (unicode-value) on the right
/// You can use the names on the page http://fortawesome.github.io/Font-Awesome/design.html
namespace fa {
enum icon {
fa_500px = 0xf26e,
addressbook = 0xf2b9,
addressbooko = 0xf2ba,
addresscard = 0xf2bb,
addresscardo = 0xf2bc,
adjust = 0xf042,
adn = 0xf170,
aligncenter = 0xf037,
alignjustify = 0xf039,
alignleft = 0xf036,
alignright = 0xf038,
amazon = 0xf270,
ambulance = 0xf0f9,
americansignlanguageinterpreting = 0xf2a3,
anchor = 0xf13d,
android = 0xf17b,
angellist = 0xf209,
angledoubledown = 0xf103,
angledoubleleft = 0xf100,
angledoubleright = 0xf101,
angledoubleup = 0xf102,
angledown = 0xf107,
angleleft = 0xf104,
angleright = 0xf105,
angleup = 0xf106,
apple = 0xf179,
archive = 0xf187,
areachart = 0xf1fe,
arrowcircledown = 0xf0ab,
arrowcircleleft = 0xf0a8,
arrowcircleodown = 0xf01a,
arrowcircleoleft = 0xf190,
arrowcircleoright = 0xf18e,
arrowcircleoup = 0xf01b,
arrowcircleright = 0xf0a9,
arrowcircleup = 0xf0aa,
arrowdown = 0xf063,
arrowleft = 0xf060,
arrowright = 0xf061,
arrowup = 0xf062,
arrows = 0xf047,
arrowsalt = 0xf0b2,
arrowsh = 0xf07e,
arrowsv = 0xf07d,
aslinterpreting = 0xf2a3,
assistivelisteningsystems = 0xf2a2,
asterisk = 0xf069,
at = 0xf1fa,
audiodescription = 0xf29e,
automobile = 0xf1b9,
backward = 0xf04a,
balancescale = 0xf24e,
ban = 0xf05e,
bandcamp = 0xf2d5,
bank = 0xf19c,
barchart = 0xf080,
barcharto = 0xf080,
barcode = 0xf02a,
bars = 0xf0c9,
bath = 0xf2cd,
bathtub = 0xf2cd,
battery = 0xf240,
battery0 = 0xf244,
battery1 = 0xf243,
battery2 = 0xf242,
battery3 = 0xf241,
battery4 = 0xf240,
batteryempty = 0xf244,
batteryfull = 0xf240,
batteryhalf = 0xf242,
batteryquarter = 0xf243,
batterythreequarters = 0xf241,
bed = 0xf236,
beer = 0xf0fc,
behance = 0xf1b4,
behancesquare = 0xf1b5,
bell = 0xf0f3,
bello = 0xf0a2,
bellslash = 0xf1f6,
bellslasho = 0xf1f7,
bicycle = 0xf206,
binoculars = 0xf1e5,
birthdaycake = 0xf1fd,
bitbucket = 0xf171,
bitbucketsquare = 0xf172,
bitcoin = 0xf15a,
blacktie = 0xf27e,
blind = 0xf29d,
bluetooth = 0xf293,
bluetoothb = 0xf294,
bold = 0xf032,
bolt = 0xf0e7,
bomb = 0xf1e2,
book = 0xf02d,
bookmark = 0xf02e,
bookmarko = 0xf097,
braille = 0xf2a1,
briefcase = 0xf0b1,
btc = 0xf15a,
bug = 0xf188,
building = 0xf1ad,
buildingo = 0xf0f7,
bullhorn = 0xf0a1,
bullseye = 0xf140,
bus = 0xf207,
buysellads = 0xf20d,
cab = 0xf1ba,
calculator = 0xf1ec,
calendar = 0xf073,
calendarchecko = 0xf274,
calendarminuso = 0xf272,
calendaro = 0xf133,
calendarpluso = 0xf271,
calendartimeso = 0xf273,
camera = 0xf030,
cameraretro = 0xf083,
car = 0xf1b9,
caretdown = 0xf0d7,
caretleft = 0xf0d9,
caretright = 0xf0da,
caretsquareodown = 0xf150,
caretsquareoleft = 0xf191,
caretsquareoright = 0xf152,
caretsquareoup = 0xf151,
caretup = 0xf0d8,
cartarrowdown = 0xf218,
cartplus = 0xf217,
cc = 0xf20a,
ccamex = 0xf1f3,
ccdinersclub = 0xf24c,
ccdiscover = 0xf1f2,
ccjcb = 0xf24b,
ccmastercard = 0xf1f1,
ccpaypal = 0xf1f4,
ccstripe = 0xf1f5,
ccvisa = 0xf1f0,
certificate = 0xf0a3,
chain = 0xf0c1,
chainbroken = 0xf127,
check = 0xf00c,
checkcircle = 0xf058,
checkcircleo = 0xf05d,
checksquare = 0xf14a,
checksquareo = 0xf046,
chevroncircledown = 0xf13a,
chevroncircleleft = 0xf137,
chevroncircleright = 0xf138,
chevroncircleup = 0xf139,
chevrondown = 0xf078,
chevronleft = 0xf053,
chevronright = 0xf054,
chevronup = 0xf077,
child = 0xf1ae,
chrome = 0xf268,
circle = 0xf111,
circleo = 0xf10c,
circleonotch = 0xf1ce,
circlethin = 0xf1db,
clipboard = 0xf0ea,
clocko = 0xf017,
clone = 0xf24d,
close = 0xf00d,
cloud = 0xf0c2,
clouddownload = 0xf0ed,
cloudupload = 0xf0ee,
cny = 0xf157,
code = 0xf121,
codefork = 0xf126,
codepen = 0xf1cb,
codiepie = 0xf284,
coffee = 0xf0f4,
cog = 0xf013,
cogs = 0xf085,
columns = 0xf0db,
comment = 0xf075,
commento = 0xf0e5,
commenting = 0xf27a,
commentingo = 0xf27b,
comments = 0xf086,
commentso = 0xf0e6,
compass = 0xf14e,
compress = 0xf066,
connectdevelop = 0xf20e,
contao = 0xf26d,
copy = 0xf0c5,
copyright = 0xf1f9,
creativecommons = 0xf25e,
creditcard = 0xf09d,
creditcardalt = 0xf283,
crop = 0xf125,
crosshairs = 0xf05b,
css3 = 0xf13c,
cube = 0xf1b2,
cubes = 0xf1b3,
cut = 0xf0c4,
cutlery = 0xf0f5,
dashboard = 0xf0e4,
dashcube = 0xf210,
database = 0xf1c0,
deaf = 0xf2a4,
deafness = 0xf2a4,
dedent = 0xf03b,
delicious = 0xf1a5,
desktop = 0xf108,
deviantart = 0xf1bd,
diamond = 0xf219,
digg = 0xf1a6,
dollar = 0xf155,
dotcircleo = 0xf192,
download = 0xf019,
dribbble = 0xf17d,
driverslicense = 0xf2c2,
driverslicenseo = 0xf2c3,
dropbox = 0xf16b,
drupal = 0xf1a9,
edge = 0xf282,
edit = 0xf044,
eercast = 0xf2da,
eject = 0xf052,
ellipsish = 0xf141,
ellipsisv = 0xf142,
empire = 0xf1d1,
envelope = 0xf0e0,
envelopeo = 0xf003,
envelopeopen = 0xf2b6,
envelopeopeno = 0xf2b7,
envelopesquare = 0xf199,
envira = 0xf299,
eraser = 0xf12d,
etsy = 0xf2d7,
eur = 0xf153,
euro = 0xf153,
exchange = 0xf0ec,
exclamation = 0xf12a,
exclamationcircle = 0xf06a,
exclamationtriangle = 0xf071,
expand = 0xf065,
expeditedssl = 0xf23e,
externallink = 0xf08e,
externallinksquare = 0xf14c,
eye = 0xf06e,
eyeslash = 0xf070,
eyedropper = 0xf1fb,
fa = 0xf2b4,
facebook = 0xf09a,
facebookf = 0xf09a,
facebookofficial = 0xf230,
facebooksquare = 0xf082,
fastbackward = 0xf049,
fastforward = 0xf050,
fax = 0xf1ac,
feed = 0xf09e,
female = 0xf182,
fighterjet = 0xf0fb,
file = 0xf15b,
filearchiveo = 0xf1c6,
fileaudioo = 0xf1c7,
filecodeo = 0xf1c9,
fileexcelo = 0xf1c3,
fileimageo = 0xf1c5,
filemovieo = 0xf1c8,
fileo = 0xf016,
filepdfo = 0xf1c1,
filephotoo = 0xf1c5,
filepictureo = 0xf1c5,
filepowerpointo = 0xf1c4,
filesoundo = 0xf1c7,
filetext = 0xf15c,
filetexto = 0xf0f6,
filevideoo = 0xf1c8,
filewordo = 0xf1c2,
filezipo = 0xf1c6,
fileso = 0xf0c5,
film = 0xf008,
filter = 0xf0b0,
fire = 0xf06d,
fireextinguisher = 0xf134,
firefox = 0xf269,
firstorder = 0xf2b0,
flag = 0xf024,
flagcheckered = 0xf11e,
flago = 0xf11d,
flash = 0xf0e7,
flask = 0xf0c3,
flickr = 0xf16e,
floppyo = 0xf0c7,
folder = 0xf07b,
foldero = 0xf114,
folderopen = 0xf07c,
folderopeno = 0xf115,
font = 0xf031,
fontawesome = 0xf2b4,
fonticons = 0xf280,
fortawesome = 0xf286,
forumbee = 0xf211,
forward = 0xf04e,
foursquare = 0xf180,
freecodecamp = 0xf2c5,
frowno = 0xf119,
futbolo = 0xf1e3,
gamepad = 0xf11b,
gavel = 0xf0e3,
gbp = 0xf154,
ge = 0xf1d1,
gear = 0xf013,
gears = 0xf085,
genderless = 0xf22d,
getpocket = 0xf265,
gg = 0xf260,
ggcircle = 0xf261,
gift = 0xf06b,
git = 0xf1d3,
gitsquare = 0xf1d2,
github = 0xf09b,
githubalt = 0xf113,
githubsquare = 0xf092,
gitlab = 0xf296,
gittip = 0xf184,
glass = 0xf000,
glide = 0xf2a5,
glideg = 0xf2a6,
globe = 0xf0ac,
google = 0xf1a0,
googleplus = 0xf0d5,
googlepluscircle = 0xf2b3,
googleplusofficial = 0xf2b3,
googleplussquare = 0xf0d4,
googlewallet = 0xf1ee,
graduationcap = 0xf19d,
gratipay = 0xf184,
grav = 0xf2d6,
group = 0xf0c0,
hsquare = 0xf0fd,
hackernews = 0xf1d4,
handgrabo = 0xf255,
handlizardo = 0xf258,
handodown = 0xf0a7,
handoleft = 0xf0a5,
handoright = 0xf0a4,
handoup = 0xf0a6,
handpapero = 0xf256,
handpeaceo = 0xf25b,
handpointero = 0xf25a,
handrocko = 0xf255,
handscissorso = 0xf257,
handspocko = 0xf259,
handstopo = 0xf256,
handshakeo = 0xf2b5,
hardofhearing = 0xf2a4,
hashtag = 0xf292,
hddo = 0xf0a0,
header = 0xf1dc,
headphones = 0xf025,
heart = 0xf004,
hearto = 0xf08a,
heartbeat = 0xf21e,
history = 0xf1da,
home = 0xf015,
hospitalo = 0xf0f8,
hotel = 0xf236,
hourglass = 0xf254,
hourglass1 = 0xf251,
hourglass2 = 0xf252,
hourglass3 = 0xf253,
hourglassend = 0xf253,
hourglasshalf = 0xf252,
hourglasso = 0xf250,
hourglassstart = 0xf251,
houzz = 0xf27c,
html5 = 0xf13b,
icursor = 0xf246,
idbadge = 0xf2c1,
idcard = 0xf2c2,
idcardo = 0xf2c3,
ils = 0xf20b,
image = 0xf03e,
imdb = 0xf2d8,
inbox = 0xf01c,
indent = 0xf03c,
industry = 0xf275,
info = 0xf129,
infocircle = 0xf05a,
inr = 0xf156,
instagram = 0xf16d,
institution = 0xf19c,
internetexplorer = 0xf26b,
intersex = 0xf224,
ioxhost = 0xf208,
italic = 0xf033,
joomla = 0xf1aa,
jpy = 0xf157,
jsfiddle = 0xf1cc,
key = 0xf084,
keyboardo = 0xf11c,
krw = 0xf159,
language = 0xf1ab,
laptop = 0xf109,
lastfm = 0xf202,
lastfmsquare = 0xf203,
leaf = 0xf06c,
leanpub = 0xf212,
legal = 0xf0e3,
lemono = 0xf094,
leveldown = 0xf149,
levelup = 0xf148,
lifebouy = 0xf1cd,
lifebuoy = 0xf1cd,
lifering = 0xf1cd,
lifesaver = 0xf1cd,
lightbulbo = 0xf0eb,
linechart = 0xf201,
link = 0xf0c1,
linkedin = 0xf0e1,
linkedinsquare = 0xf08c,
linode = 0xf2b8,
fa_linux = 0xf17c,
list = 0xf03a,
listalt = 0xf022,
listol = 0xf0cb,
listul = 0xf0ca,
locationarrow = 0xf124,
lock = 0xf023,
longarrowdown = 0xf175,
longarrowleft = 0xf177,
longarrowright = 0xf178,
longarrowup = 0xf176,
lowvision = 0xf2a8,
magic = 0xf0d0,
magnet = 0xf076,
mailforward = 0xf064,
mailreply = 0xf112,
mailreplyall = 0xf122,
male = 0xf183,
map = 0xf279,
mapmarker = 0xf041,
mapo = 0xf278,
mappin = 0xf276,
mapsigns = 0xf277,
mars = 0xf222,
marsdouble = 0xf227,
marsstroke = 0xf229,
marsstrokeh = 0xf22b,
marsstrokev = 0xf22a,
maxcdn = 0xf136,
meanpath = 0xf20c,
medium = 0xf23a,
medkit = 0xf0fa,
meetup = 0xf2e0,
meho = 0xf11a,
mercury = 0xf223,
microchip = 0xf2db,
microphone = 0xf130,
microphoneslash = 0xf131,
minus = 0xf068,
minuscircle = 0xf056,
minussquare = 0xf146,
minussquareo = 0xf147,
mixcloud = 0xf289,
mobile = 0xf10b,
mobilephone = 0xf10b,
modx = 0xf285,
money = 0xf0d6,
moono = 0xf186,
mortarboard = 0xf19d,
motorcycle = 0xf21c,
mousepointer = 0xf245,
music = 0xf001,
navicon = 0xf0c9,
neuter = 0xf22c,
newspapero = 0xf1ea,
objectgroup = 0xf247,
objectungroup = 0xf248,
odnoklassniki = 0xf263,
odnoklassnikisquare = 0xf264,
opencart = 0xf23d,
openid = 0xf19b,
opera = 0xf26a,
optinmonster = 0xf23c,
outdent = 0xf03b,
pagelines = 0xf18c,
paintbrush = 0xf1fc,
paperplane = 0xf1d8,
paperplaneo = 0xf1d9,
paperclip = 0xf0c6,
paragraph = 0xf1dd,
paste = 0xf0ea,
pause = 0xf04c,
pausecircle = 0xf28b,
pausecircleo = 0xf28c,
paw = 0xf1b0,
paypal = 0xf1ed,
pencil = 0xf040,
pencilsquare = 0xf14b,
pencilsquareo = 0xf044,
percent = 0xf295,
phone = 0xf095,
phonesquare = 0xf098,
photo = 0xf03e,
pictureo = 0xf03e,
piechart = 0xf200,
piedpiper = 0xf2ae,
piedpiperalt = 0xf1a8,
piedpiperpp = 0xf1a7,
pinterest = 0xf0d2,
pinterestp = 0xf231,
pinterestsquare = 0xf0d3,
plane = 0xf072,
play = 0xf04b,
playcircle = 0xf144,
playcircleo = 0xf01d,
plug = 0xf1e6,
plus = 0xf067,
pluscircle = 0xf055,
plussquare = 0xf0fe,
plussquareo = 0xf196,
podcast = 0xf2ce,
poweroff = 0xf011,
print = 0xf02f,
producthunt = 0xf288,
puzzlepiece = 0xf12e,
qq = 0xf1d6,
qrcode = 0xf029,
question = 0xf128,
questioncircle = 0xf059,
questioncircleo = 0xf29c,
quora = 0xf2c4,
quoteleft = 0xf10d,
quoteright = 0xf10e,
ra = 0xf1d0,
random = 0xf074,
ravelry = 0xf2d9,
rebel = 0xf1d0,
recycle = 0xf1b8,
reddit = 0xf1a1,
redditalien = 0xf281,
redditsquare = 0xf1a2,
refresh = 0xf021,
registered = 0xf25d,
remove = 0xf00d,
renren = 0xf18b,
reorder = 0xf0c9,
repeat = 0xf01e,
reply = 0xf112,
replyall = 0xf122,
resistance = 0xf1d0,
retweet = 0xf079,
rmb = 0xf157,
road = 0xf018,
rocket = 0xf135,
rotateleft = 0xf0e2,
rotateright = 0xf01e,
rouble = 0xf158,
rss = 0xf09e,
rsssquare = 0xf143,
rub = 0xf158,
ruble = 0xf158,
rupee = 0xf156,
s15 = 0xf2cd,
safari = 0xf267,
save = 0xf0c7,
scissors = 0xf0c4,
scribd = 0xf28a,
search = 0xf002,
searchminus = 0xf010,
searchplus = 0xf00e,
sellsy = 0xf213,
send = 0xf1d8,
sendo = 0xf1d9,
server = 0xf233,
share = 0xf064,
sharealt = 0xf1e0,
sharealtsquare = 0xf1e1,
sharesquare = 0xf14d,
sharesquareo = 0xf045,
shekel = 0xf20b,
sheqel = 0xf20b,
shield = 0xf132,
ship = 0xf21a,
shirtsinbulk = 0xf214,
shoppingbag = 0xf290,
shoppingbasket = 0xf291,
shoppingcart = 0xf07a,
shower = 0xf2cc,
signin = 0xf090,
signlanguage = 0xf2a7,
signout = 0xf08b,
signal = 0xf012,
signing = 0xf2a7,
simplybuilt = 0xf215,
sitemap = 0xf0e8,
skyatlas = 0xf216,
skype = 0xf17e,
slack = 0xf198,
sliders = 0xf1de,
slideshare = 0xf1e7,
smileo = 0xf118,
snapchat = 0xf2ab,
snapchatghost = 0xf2ac,
snapchatsquare = 0xf2ad,
snowflakeo = 0xf2dc,
soccerballo = 0xf1e3,
sort = 0xf0dc,
sortalphaasc = 0xf15d,
sortalphadesc = 0xf15e,
sortamountasc = 0xf160,
sortamountdesc = 0xf161,
sortasc = 0xf0de,
sortdesc = 0xf0dd,
sortdown = 0xf0dd,
sortnumericasc = 0xf162,
sortnumericdesc = 0xf163,
sortup = 0xf0de,
soundcloud = 0xf1be,
spaceshuttle = 0xf197,
spinner = 0xf110,
spoon = 0xf1b1,
spotify = 0xf1bc,
square = 0xf0c8,
squareo = 0xf096,
stackexchange = 0xf18d,
stackoverflow = 0xf16c,
star = 0xf005,
starhalf = 0xf089,
starhalfempty = 0xf123,
starhalffull = 0xf123,
starhalfo = 0xf123,
staro = 0xf006,
steam = 0xf1b6,
steamsquare = 0xf1b7,
stepbackward = 0xf048,
stepforward = 0xf051,
stethoscope = 0xf0f1,
stickynote = 0xf249,
stickynoteo = 0xf24a,
stop = 0xf04d,
stopcircle = 0xf28d,
stopcircleo = 0xf28e,
streetview = 0xf21d,
strikethrough = 0xf0cc,
stumbleupon = 0xf1a4,
stumbleuponcircle = 0xf1a3,
subscript = 0xf12c,
subway = 0xf239,
suitcase = 0xf0f2,
suno = 0xf185,
superpowers = 0xf2dd,
superscript = 0xf12b,
support = 0xf1cd,
table = 0xf0ce,
tablet = 0xf10a,
tachometer = 0xf0e4,
tag = 0xf02b,
tags = 0xf02c,
tasks = 0xf0ae,
taxi = 0xf1ba,
telegram = 0xf2c6,
television = 0xf26c,
tencentweibo = 0xf1d5,
terminal = 0xf120,
textheight = 0xf034,
textwidth = 0xf035,
th = 0xf00a,
thlarge = 0xf009,
thlist = 0xf00b,
themeisle = 0xf2b2,
thermometer = 0xf2c7,
thermometer0 = 0xf2cb,
thermometer1 = 0xf2ca,
thermometer2 = 0xf2c9,
thermometer3 = 0xf2c8,
thermometer4 = 0xf2c7,
thermometerempty = 0xf2cb,
thermometerfull = 0xf2c7,
thermometerhalf = 0xf2c9,
thermometerquarter = 0xf2ca,
thermometerthreequarters = 0xf2c8,
thumbtack = 0xf08d,
thumbsdown = 0xf165,
thumbsodown = 0xf088,
thumbsoup = 0xf087,
thumbsup = 0xf164,
ticket = 0xf145,
times = 0xf00d,
timescircle = 0xf057,
timescircleo = 0xf05c,
timesrectangle = 0xf2d3,
timesrectangleo = 0xf2d4,
tint = 0xf043,
toggledown = 0xf150,
toggleleft = 0xf191,
toggleoff = 0xf204,
toggleon = 0xf205,
toggleright = 0xf152,
toggleup = 0xf151,
trademark = 0xf25c,
train = 0xf238,
transgender = 0xf224,
transgenderalt = 0xf225,
trash = 0xf1f8,
trasho = 0xf014,
tree = 0xf1bb,
trello = 0xf181,
tripadvisor = 0xf262,
trophy = 0xf091,
truck = 0xf0d1,
fa_try = 0xf195,
tty = 0xf1e4,
tumblr = 0xf173,
tumblrsquare = 0xf174,
turkishlira = 0xf195,
tv = 0xf26c,
twitch = 0xf1e8,
twitter = 0xf099,
twittersquare = 0xf081,
umbrella = 0xf0e9,
underline = 0xf0cd,
undo = 0xf0e2,
universalaccess = 0xf29a,
university = 0xf19c,
unlink = 0xf127,
unlock = 0xf09c,
unlockalt = 0xf13e,
unsorted = 0xf0dc,
upload = 0xf093,
usb = 0xf287,
usd = 0xf155,
user = 0xf007,
usercircle = 0xf2bd,
usercircleo = 0xf2be,
usermd = 0xf0f0,
usero = 0xf2c0,
userplus = 0xf234,
usersecret = 0xf21b,
usertimes = 0xf235,
users = 0xf0c0,
vcard = 0xf2bb,
vcardo = 0xf2bc,
venus = 0xf221,
venusdouble = 0xf226,
venusmars = 0xf228,
viacoin = 0xf237,
viadeo = 0xf2a9,
viadeosquare = 0xf2aa,
videocamera = 0xf03d,
vimeo = 0xf27d,
vimeosquare = 0xf194,
vine = 0xf1ca,
vk = 0xf189,
volumecontrolphone = 0xf2a0,
volumedown = 0xf027,
volumeoff = 0xf026,
volumeup = 0xf028,
warning = 0xf071,
wechat = 0xf1d7,
weibo = 0xf18a,
weixin = 0xf1d7,
whatsapp = 0xf232,
wheelchair = 0xf193,
wheelchairalt = 0xf29b,
wifi = 0xf1eb,
wikipediaw = 0xf266,
windowclose = 0xf2d3,
windowcloseo = 0xf2d4,
windowmaximize = 0xf2d0,
windowminimize = 0xf2d1,
windowrestore = 0xf2d2,
windows = 0xf17a,
won = 0xf159,
wordpress = 0xf19a,
wpbeginner = 0xf297,
wpexplorer = 0xf2de,
wpforms = 0xf298,
wrench = 0xf0ad,
xing = 0xf168,
xingsquare = 0xf169,
ycombinator = 0xf23b,
ycombinatorsquare = 0xf1d4,
yahoo = 0xf19e,
yc = 0xf23b,
ycsquare = 0xf1d4,
yelp = 0xf1e9,
yen = 0xf157,
yoast = 0xf2b1,
youtube = 0xf167,
youtubeplay = 0xf16a,
youtubesquare = 0xf166
};
}
//---------------------------------------------------------------------------------------
class QtAwesomeIconPainter;
/// The main class for managing icons
/// This class requires a 2-phase construction. You must first create the class and then initialize it via an init* method
class QtAwesome : public QObject
{
Q_OBJECT
public:
explicit QtAwesome(QObject *parent = 0);
virtual ~QtAwesome();
void init( const QString& fontname );
bool initFontAwesome();
void addNamedCodepoint( const QString& name, int codePoint );
QHash<QString,int> namedCodePoints() { return namedCodepoints_; }
void setDefaultOption( const QString& name, const QVariant& value );
QVariant defaultOption( const QString& name );
QIcon icon( int character, const QVariantMap& options = QVariantMap() );
QIcon icon( const QString& name, const QVariantMap& options = QVariantMap() );
QIcon icon(QtAwesomeIconPainter* painter, const QVariantMap& optionMap = QVariantMap() );
void give( const QString& name, QtAwesomeIconPainter* painter );
QFont font( int size );
/// Returns the font-name that is used as icon-map
QString fontName() { return fontName_ ; }
private:
QString fontName_; ///< The font name used for this map
QHash<QString,int> namedCodepoints_; ///< A map with names mapped to code-points
QHash<QString, QtAwesomeIconPainter*> painterMap_; ///< A map of custom painters
QVariantMap defaultOptions_; ///< The default icon options
QtAwesomeIconPainter* fontIconPainter_; ///< A special painter fo painting codepoints
};
//---------------------------------------------------------------------------------------
/// The QtAwesomeIconPainter is a specialized painter for painting icons
/// your can implement an iconpainter to create custom font-icon code
class QtAwesomeIconPainter
{
public:
virtual ~QtAwesomeIconPainter() {}
virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rect, QIcon::Mode mode, QIcon::State state, const QVariantMap& options ) = 0;
};
Q_DECLARE_METATYPE(QtAwesomeAnimation*)
#endif // QTAWESOME_H

12
thirdparty/QtAwesome/QtAwesome/QtAwesome.pri vendored Executable file
View File

@ -0,0 +1,12 @@
INCLUDEPATH += $$PWD
SOURCES += $$PWD/QtAwesome.cpp \
$$PWD/QtAwesomeAnim.cpp
HEADERS += $$PWD/QtAwesome.h \
$$PWD/QtAwesomeAnim.h
RESOURCES += $$PWD/QtAwesome.qrc

29
thirdparty/QtAwesome/QtAwesome/QtAwesome.pro vendored Executable file
View File

@ -0,0 +1,29 @@
#-------------------------------------------------
#
# Project created by QtCreator 2013-04-18T13:28:42
#
#-------------------------------------------------
TARGET = QtAwesome
TEMPLATE = lib
CONFIG += staticlib c++11
QT += widgets
SOURCES += QtAwesome.cpp QtAwesomeAnim.cpp
HEADERS += QtAwesome.h QtAwesomeAnim.h
isEmpty(PREFIX) {
unix {
PREFIX = /usr
} else {
PREFIX = $$[QT_INSTALL_PREFIX]
}
}
install_headers.files = QtAwesome.h QtAwesomeAnim.h
install_headers.path = $$PREFIX/include
target.path = $$PREFIX/lib
INSTALLS += install_headers target
RESOURCES += \
QtAwesome.qrc

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>fonts/fontawesome-4.7.0.ttf</file>
</qresource>
</RCC>

View File

@ -0,0 +1,46 @@
#include "QtAwesomeAnim.h"
#include <cmath>
#include <QPainter>
#include <QRect>
#include <QTimer>
#include <QWidget>
QtAwesomeAnimation::QtAwesomeAnimation(QWidget *parentWidget, int interval, int step)
: parentWidgetRef_( parentWidget )
, timer_( 0 )
, interval_( interval )
, step_( step )
, angle_( 0.0f )
{
}
void QtAwesomeAnimation::setup( QPainter &painter, const QRect &rect)
{
// first time set the timer
if( !timer_ )
{
timer_ = new QTimer();
connect(timer_,SIGNAL(timeout()), this, SLOT(update()) );
timer_->start(interval_);
}
else
{
//timer, angle, self.step = self.info[self.parent_widget]
float x_center = rect.width() * 0.5;
float y_center = rect.height() * 0.5;
painter.translate(x_center, y_center);
painter.rotate(angle_);
painter.translate(-x_center, -y_center);
}
}
void QtAwesomeAnimation::update()
{
angle_ += step_;
angle_ = std::fmod( angle_, 360);
parentWidgetRef_->update();
}

View File

@ -0,0 +1,36 @@
#ifndef QTAWESOMEANIMATION_H
#define QTAWESOMEANIMATION_H
#include <QObject>
class QPainter;
class QRect;
class QTimer;
class QWidget;
///
/// Basic Animation Support for QtAwesome (Inspired by https://github.com/spyder-ide/qtawesome)
///
class QtAwesomeAnimation : public QObject
{
Q_OBJECT
public:
QtAwesomeAnimation( QWidget* parentWidget, int interval=10, int step=1);
void setup( QPainter& painter, const QRect& rect );
public slots:
void update();
private:
QWidget* parentWidgetRef_;
QTimer* timer_;
int interval_;
int step_;
float angle_;
};
#endif // QTAWESOMEANIMATION_H

Binary file not shown.

View File

@ -0,0 +1,20 @@
#-------------------------------------------------
#
# Project created by QtCreator 2013-04-18T15:05:07
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = QtAwesomeSample
TEMPLATE = app
SOURCES += main.cpp
HEADERS +=
include(../QtAwesome/QtAwesome.pri)

63
thirdparty/QtAwesome/QtAwesomeSample/main.cpp vendored Executable file
View File

@ -0,0 +1,63 @@
/**
* MIT Licensed
*
* Copyright 2011-2015 - Reliable Bits Software by Blommers IT. All Rights Reserved.
* Author Rick Blommers
*/
#include "QtAwesome.h"
#include <QApplication>
#include <QMainWindow>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QMainWindow w;
QtAwesome* awesome = new QtAwesome(&w);
awesome->initFontAwesome();
QVBoxLayout* layout = new QVBoxLayout();
// a simple beer button
//=====================
{
QPushButton* beerButton = new QPushButton( "Cheers!");
QVariantMap options;
options.insert("anim", qVariantFromValue( new QtAwesomeAnimation(beerButton) ) );
beerButton->setIcon( awesome->icon( fa::beer, options ) );
layout->addWidget(beerButton);
}
// a simple beer checkbox button
//==============================
{
QPushButton* toggleButton = new QPushButton("Toggle Me");
toggleButton->setCheckable(true);
QVariantMap options;
options.insert("color", QColor(Qt::green) );
options.insert("text-off", QString(fa::squareo) );
options.insert("color-off", QColor(Qt::red) );
toggleButton->setIcon( awesome->icon( fa::checksquareo, options ));
layout->addWidget(toggleButton);
}
// add the samples
QWidget* samples = new QWidget();
samples->setLayout(layout);
w.setCentralWidget(samples);
w.show();
return app.exec();
}

230
thirdparty/QtAwesome/README.md vendored Executable file
View File

@ -0,0 +1,230 @@
QtAwesome - Font Awesome support for Qt applications
====================================================
Description
-----------
QtAwesome is a simple library that can be used to add [Font Awesome](http://fortawesome.github.io/Font-Awesome/) icons to your [Qt application](http://qt-project.org/).
NOTE: Though the name is QtAwesome and currently it's very Font Awesome based, you can use every other icon/glyph font you want.
The class can also be used to manage your own dynamic code-drawn icons, by adding named icon-painters.
Updated to FontAwesome 4.7.0
----------------------------
This library has been updated to Font Awesome version **4.7.0**.
* In the 4.5.0 version the _linux name has been changed to fa_linux. (Makes the naming of conflicting/invalid names more consistent, like fa_try and fa_500px)
* You can find the previous FontAwesome 4 c++11 library in the [c++11 branch](https://github.com/gamecreature/QtAwesome/tree/c++11).
* You can find the previous FontAwesome 3 library in the [fontawesome-3 branch](https://github.com/gamecreature/QtAwesome/tree/fontawesome-3).
**Note about previous c++11**
I removed the C++11 requirement. And moved the c++11 code to a c++11 branch.
It's not that I don't like c++11, but the typed enum made the code less flexible then it is now.
Just integers it is. Simpler is better.
Installation
------------
The easiest way to include QtAweome in your project is to copy the QtAwesome directory to your
project tree and add the following `include()` to your Qt project file:
include(QtAwesome/QtAwesome.pri)
Now you are good to go!
Usage
-----
You probably want to create a single QtAwesome object for your whole application:
````
QtAwesome* awesome = new QtAwesome( qApp )
awesome->initFontAwesome(); // This line is important as it loads the font and initializes the named icon map
````
* Add an accessor to this object (i.e. a global function, member of your application object, or whatever you like).
* Use an icon name from the [Font Awesome Cheatsheet](http://fortawesome.github.io/Font-Awesome/cheatsheet/).
Example
--------
```c++
// You should create a single object of QtAwesome.
QtAwesome* awesome = new QtAwesome( qApp );
awesome->initFontAwesome();
// Next create your icon with the help of the icon-enumeration (no dashes):
QPushButton* beerButton new QPushButton( awesome->icon( fa::beer ), "Cheers!" );
// You can also use 'string' names to access the icons. (The string version omits the 'fa-' or 'icon-' prefix and has no dashes )
QPushButton* coffeeButton new QPushButton( awesome->icon( "coffee" ), "Black please!" );
// When you create an icon you can supply some options for your icons:
// The available options can be found at the "Default options"-section
QVariantMap options;
options.insert( "color" , QColor(255,0,0) );
QPushButton* musicButton = new QPushButton( awesome->icon( fa::music, options ), "Music" );
// You can also change the default options.
// for example if you always would like to have green icons you could call)
awesome->setDefaultOption( "color-disabled", QColor(0,255,0) );
// You can also directly render a label with this font
QLabel* label = new QLabel( QChar( fa::group ) );
label->setFont( awesome->font(16) );
```
Example custom painter
----------------------
This example registers a custom painter for supporting a duplicate icon (it draws 2 "plus marks"):
```c++
class DuplicateIconPainter : public QtAwesomeIconPainter
{
public:
virtual void paint( QtAwesome* awesome, QPainter* painter, const QRect& rectIn, QIcon::Mode mode, QIcon::State state, const QVariantMap& options )
{
int drawSize = qRound(rectIn.height()*0.5);
int offset = rectIn.height() / 4;
QChar chr = QChar( static_cast<int>(fa::plus) );
painter->setFont( awesome->font( drawSize ) );
painter->setPen( QColor(100,100,100) );
painter->drawText( QRect( QPoint(offset*2, offset*2), QSize(drawSize, drawSize) ), chr , QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) );
painter->setPen( QColor(50,50,50) );
painter->drawText( QRect( QPoint(rectIn.width()-drawSize-offset, rectIn.height()-drawSize-offset), QSize(drawSize, drawSize) ), chr , QTextOption( Qt::AlignCenter|Qt::AlignVCenter ) );
}
};
awesome->give("duplicate", new DuplicateIconPainter() );
```
Default options:
----------------
The following options are default in the QtAwesome class.
```c++
setDefaultOption( "color", QColor(50,50,50) );
setDefaultOption( "color-disabled", QColor(70,70,70,60));
setDefaultOption( "color-active", QColor(10,10,10));
setDefaultOption( "color-selected", QColor(10,10,10));
setDefaultOption( "text", QString() ); // internal option
setDefaultOption( "text-disabled", QString() );
setDefaultOption( "text-active", QString() );
setDefaultOption( "text-selected", QString() );
setDefaultOption( "scale-factor", 0.9 );
```
When creating an icon, it first populates the options-map with the default options from the QtAwesome object.
After that the options are expanded/overwritten by the options supplied to the icon.
It is possible to use another glyph per icon-state. For example to make an icon-unlock symbol switch to locked when selected,
you could supply the following option:
```c++
options.insert("text-selected", QString( fa::lock ) );
```
Color and text options have the following structure:
`keyname-iconmode-iconstate`
Where iconmode normal is empty
And iconstate On is off.
So the list of items used is:
- color
- color-disabled
- color-active
- color-selected
- color-off
- color-disabled-off
- color-active-off
- color-selected-off
- text
- text-disabled
- text-active
- text-selected
- text-off
- text-disabled-off
- text-active-off
- text-selected-off
License
-------
MIT License. Copyright 2013 - Reliable Bits Software by Blommers IT. [http://blommersit.nl/](http://blommersit.nl)
The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL)
The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/)
"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
Contact
-------
* email: <rick@blommersit.nl>
* twitter: [https://twitter.com/gamecreature](https://twitter.com/gamecreature)
* website: [http://blommersit.nl](http://blommersit.nl) (warning Dutch content ahead)
* github: [https://github.com/gamecreature/QtAwesome](https://github.com/gamecreature/QtAwesome)
Known issues and workarounds
----------------------------
On Mac OS X, placing an qtAwesome icon in QMainWindow menu, doesn't work directly.
See the following issue: [https://github.com/gamecreature/QtAwesome/issues/10]
A workaround for this problem is converting it to a Pixmap icon like this:
```c++
QAction* menuAction = new QAction("test");
menuAction->setIcon( awesome->icon(fa::beer).pixmap(32,32) );
```
Remarks
-------
I've created this project because I needed some nice icons for my own Qt project. After doing a lot of
css/html5 work and being spoiled by the ease of twitter bootstrap with Font Awesome,
I thought it would be nice to be able to use these icons for my Qt project.
I've slightly changed the code from the original, added some more documentation, but it's still
a work in progress. So feel free to drop me an e-mail for your suggestions and improvements!
There are still some things todo, like:
* document the usage of another icon font
* add some tests
* do some code cleanup
Thanks go to the contributors of this project!
And of course last but not least,
Many thanks go to Dave Gandy an the other Font Awesome contributors!! [http://fortawesome.github.com/Font-Awesome](http://fortawesome.github.com/Font-Awesome)
And of course to the Qt team/contributors for supplying this great cross-platform c++ library.
Contributions are welcome! Feel free to fork and send a pull request through Github.