Add cut face customizing.

This commit introduce a new tool, allow user to customize the face template for automatical extruding.
master
Jeremy Hu 2019-02-24 23:12:23 +09:30
parent 7491679425
commit 02bb32391e
19 changed files with 643 additions and 32 deletions

View File

@ -287,6 +287,12 @@ HEADERS += src/posedocument.h
SOURCES += src/combinemode.cpp
HEADERS += src/combinemode.h
SOURCES += src/cutdocument.cpp
HEADERS += src/cutdocument.h
SOURCES += src/cuttemplate.cpp
HEADERS += src/cuttemplate.h
SOURCES += src/main.cpp
HEADERS += src/version.h

201
src/cutdocument.cpp Normal file
View File

@ -0,0 +1,201 @@
#include <QDebug>
#include <QApplication>
#include "cutdocument.h"
const float CutDocument::m_nodeRadius = 0.05;
const float CutDocument::m_nodeScaleFactor = 0.5;
bool CutDocument::hasPastableNodesInClipboard() const
{
return false;
}
bool CutDocument::originSettled() const
{
return false;
}
bool CutDocument::isNodeEditable(QUuid nodeId) const
{
Q_UNUSED(nodeId);
return true;
}
bool CutDocument::isEdgeEditable(QUuid edgeId) const
{
Q_UNUSED(edgeId);
return true;
}
void CutDocument::copyNodes(std::set<QUuid> nodeIdSet) const
{
Q_UNUSED(nodeIdSet);
}
void CutDocument::saveHistoryItem()
{
CutHistoryItem item;
toCutTemplate(item.cutTemplate);
m_undoItems.push_back(item);
}
bool CutDocument::undoable() const
{
return m_undoItems.size() >= 2;
}
bool CutDocument::redoable() const
{
return !m_redoItems.empty();
}
void CutDocument::undo()
{
if (!undoable())
return;
m_redoItems.push_back(m_undoItems.back());
m_undoItems.pop_back();
const auto &item = m_undoItems.back();
fromCutTemplate(item.cutTemplate);
}
void CutDocument::redo()
{
if (m_redoItems.empty())
return;
m_undoItems.push_back(m_redoItems.back());
const auto &item = m_redoItems.back();
fromCutTemplate(item.cutTemplate);
m_redoItems.pop_back();
}
void CutDocument::paste()
{
// void
}
void CutDocument::reset()
{
nodeMap.clear();
edgeMap.clear();
partMap.clear();
m_cutNodeIds.clear();
emit cleanup();
emit cutTemplateChanged();
}
void CutDocument::clearHistories()
{
m_undoItems.clear();
m_redoItems.clear();
}
void CutDocument::toCutTemplate(std::vector<QVector2D> &cutTemplate)
{
for (const auto &nodeId: m_cutNodeIds) {
auto findNode = nodeMap.find(nodeId);
if (findNode == nodeMap.end())
continue;
QVector2D position = nodeToCutPosition({findNode->second.x,
findNode->second.y,
findNode->second.z
});
cutTemplate.push_back(position);
}
}
QVector2D CutDocument::nodeToCutPosition(const QVector3D &nodePosition)
{
return {(nodePosition.x() * 2 - (float)1) / m_nodeScaleFactor,
(nodePosition.y() * 2 - (float)1) / m_nodeScaleFactor};
}
QVector3D CutDocument::cutToNodePosition(const QVector2D &cutPosition)
{
return {(cutPosition.x() * m_nodeScaleFactor + 1) * (float)0.5,
(cutPosition.y() * m_nodeScaleFactor + 1) * (float)0.5,
(float)0};
}
void CutDocument::fromCutTemplate(const std::vector<QVector2D> &cutTemplate)
{
reset();
std::set<QUuid> newAddedNodeIds;
std::set<QUuid> newAddedEdgeIds;
m_partId = QUuid::createUuid();
auto &part = partMap[m_partId];
part.id = m_partId;
for (const auto &position: cutTemplate) {
SkeletonNode node;
node.partId = m_partId;
node.id = QUuid::createUuid();
node.setRadius(m_nodeRadius);
auto nodePosition = cutToNodePosition(position);
node.x = nodePosition.x();
node.y = nodePosition.y();
node.z = nodePosition.z();
nodeMap[node.id] = node;
newAddedNodeIds.insert(node.id);
m_cutNodeIds.push_back(node.id);
}
for (size_t i = 0; i < m_cutNodeIds.size(); ++i) {
size_t j = (i + 1) % m_cutNodeIds.size();
const QUuid &firstNodeId = m_cutNodeIds[i];
const QUuid &secondNodeId = m_cutNodeIds[j];
SkeletonEdge edge;
edge.partId = m_partId;
edge.id = QUuid::createUuid();
edge.nodeIds.push_back(firstNodeId);
edge.nodeIds.push_back(secondNodeId);
edgeMap[edge.id] = edge;
newAddedEdgeIds.insert(edge.id);
nodeMap[firstNodeId].edgeIds.push_back(edge.id);
nodeMap[secondNodeId].edgeIds.push_back(edge.id);
}
for (const auto &nodeIt: newAddedNodeIds) {
emit nodeAdded(nodeIt);
}
for (const auto &edgeIt: newAddedEdgeIds) {
emit edgeAdded(edgeIt);
}
emit cutTemplateChanged();
}
void CutDocument::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(it->first);
emit cutTemplateChanged();
}
void CutDocument::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;
auto part = partMap.find(it->second.partId);
if (part != partMap.end())
part->second.dirty = true;
emit nodeOriginChanged(nodeId);
emit cutTemplateChanged();
}

60
src/cutdocument.h Normal file
View File

@ -0,0 +1,60 @@
#ifndef DUST3D_CUT_DOCUMENT_H
#define DUST3D_CUT_DOCUMENT_H
#include <deque>
#include <QVector2D>
#include "skeletondocument.h"
struct CutHistoryItem
{
std::vector<QVector2D> cutTemplate;
};
class CutDocument : public SkeletonDocument
{
Q_OBJECT
signals:
void cleanup();
void nodeAdded(QUuid nodeId);
void edgeAdded(QUuid edgeId);
void nodeOriginChanged(QUuid nodeId);
void cutTemplateChanged();
public:
bool undoable() const override;
bool redoable() const override;
bool hasPastableNodesInClipboard() const override;
bool originSettled() const override;
bool isNodeEditable(QUuid nodeId) const override;
bool isEdgeEditable(QUuid edgeId) const override;
void copyNodes(std::set<QUuid> nodeIdSet) const override;
void reset();
void toCutTemplate(std::vector<QVector2D> &cutTemplate);
void fromCutTemplate(const std::vector<QVector2D> &cutTemplate);
public slots:
void saveHistoryItem();
void clearHistories();
void undo() override;
void redo() override;
void paste() override;
void moveNodeBy(QUuid nodeId, float x, float y, float z);
void setNodeOrigin(QUuid nodeId, float x, float y, float z);
public:
static const float m_nodeRadius;
static const float m_nodeScaleFactor;
private:
std::deque<CutHistoryItem> m_undoItems;
std::deque<CutHistoryItem> m_redoItems;
std::vector<QUuid> m_cutNodeIds;
QUuid m_partId;
QVector2D nodeToCutPosition(const QVector3D &nodePosition);
QVector3D cutToNodePosition(const QVector2D &cutPosition);
};
#endif

83
src/cuttemplate.cpp Normal file
View File

@ -0,0 +1,83 @@
#include <QStringList>
#include "cuttemplate.h"
IMPL_CutTemplateToDispName
TMPL_CutTemplateToPoints
static const auto g_defaultCutTemplate = CutTemplateToPoints(CutTemplate::Quad);
bool cutTemplatePointsCompare(const std::vector<QVector2D> &first, const std::vector<QVector2D> &second)
{
if (first.size() != second.size())
return false;
for (size_t i = 0; i < first.size(); ++i) {
if (!qFuzzyCompare(first[i], second[i]))
return false;
}
return true;
}
QString cutTemplatePointsToString(const std::vector<QVector2D> &points)
{
QStringList items;
for (const auto &point: points) {
items.append(QString::number(point.x()));
items.append(QString::number(point.y()));
}
return items.join(",");
}
std::vector<QVector2D> cutTemplatePointsFromString(const QString &pointsString)
{
std::vector<float> numbers;
for (const auto &item: pointsString.split(",")) {
numbers.push_back(item.toFloat());
}
if (0 != numbers.size() % 2)
return g_defaultCutTemplate;
size_t pointsNum = numbers.size() / 2;
if (pointsNum < 3)
return g_defaultCutTemplate;
size_t numberIndex = 0;
std::vector<QVector2D> points;
for (size_t i = 0; i < pointsNum; ++i) {
const auto &x = numbers[numberIndex++];
const auto &y = numbers[numberIndex++];
points.push_back({x, y});
}
return points;
}
void normalizeCutTemplatePoints(std::vector<QVector2D> *points)
{
QVector2D center;
if (nullptr == points || points->empty())
return;
float xLow = std::numeric_limits<float>::max();
float xHigh = std::numeric_limits<float>::lowest();
float yLow = std::numeric_limits<float>::max();
float yHigh = std::numeric_limits<float>::lowest();
for (const auto &position: *points) {
if (position.x() < xLow)
xLow = position.x();
else if (position.x() > xHigh)
xHigh = position.x();
if (position.y() < yLow)
yLow = position.y();
else if (position.y() > yHigh)
yHigh = position.y();
}
float xMiddle = (xHigh + xLow) * 0.5;
float yMiddle = (yHigh + yLow) * 0.5;
float xSize = xHigh - xLow;
float ySize = yHigh - yLow;
float longSize = ySize;
if (xSize > longSize)
longSize = xSize;
if (qFuzzyIsNull(longSize))
longSize = 0.000001;
for (auto &position: *points) {
position.setX((position.x() - xMiddle) * 2 / longSize);
position.setY((position.y() - yMiddle) * 2 / longSize);
}
}

53
src/cuttemplate.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef CUT_TEMPLATE_H
#define CUT_TEMPLATE_H
#include <QObject>
#include <QString>
#include <QVector2D>
#include <vector>
enum class CutTemplate
{
Quad = 0,
//Octagon,
Count
};
QString CutTemplateToDispName(CutTemplate cutTemplate);
#define IMPL_CutTemplateToDispName \
QString CutTemplateToDispName(CutTemplate cutTemplate) \
{ \
switch (cutTemplate) { \
case CutTemplate::Quad: \
return QObject::tr("Quad"); \
default: \
return ""; \
} \
}
std::vector<QVector2D> CutTemplateToPoints(CutTemplate cutTemplate);
#define TMPL_CutTemplateToPoints \
std::vector<QVector2D> CutTemplateToPoints(CutTemplate cutTemplate) \
{ \
switch (cutTemplate) { \
case CutTemplate::Quad: \
return { \
{-1.0, -1.0}, \
{ 1.0, -1.0}, \
{ 1.0, 1.0}, \
{-1.0, 1.0}, \
}; \
default: \
return { \
{-1.0, -1.0}, \
{ 1.0, -1.0}, \
{ 1.0, 1.0}, \
{-1.0, 1.0}, \
}; \
} \
}
bool cutTemplatePointsCompare(const std::vector<QVector2D> &first, const std::vector<QVector2D> &second);
QString cutTemplatePointsToString(const std::vector<QVector2D> &points);
std::vector<QVector2D> cutTemplatePointsFromString(const QString &pointsString);
void normalizeCutTemplatePoints(std::vector<QVector2D> *points);
#endif

View File

@ -861,6 +861,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
part["rounded"] = partIt.second.rounded ? "true" : "false";
if (partIt.second.cutRotationAdjusted())
part["cutRotation"] = QString::number(partIt.second.cutRotation);
if (partIt.second.cutTemplateAdjusted())
part["cutTemplate"] = cutTemplatePointsToString(partIt.second.cutTemplate);
part["dirty"] = partIt.second.dirty ? "true" : "false";
if (partIt.second.hasColor)
part["color"] = partIt.second.color.name();
@ -1118,6 +1120,9 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
const auto &cutRotationIt = partKv.second.find("cutRotation");
if (cutRotationIt != partKv.second.end())
part.setCutRotation(cutRotationIt->second.toFloat());
const auto &cutTemplateIt = partKv.second.find("cutTemplate");
if (cutTemplateIt != partKv.second.end())
part.setCutTemplate(cutTemplatePointsFromString(cutTemplateIt->second));
if (isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "inverse")))
inversePartIds.insert(part.id);
const auto &colorIt = partKv.second.find("color");
@ -2200,12 +2205,29 @@ void Document::setPartCutRotation(QUuid partId, float cutRotation)
qDebug() << "Part not found:" << partId;
return;
}
if (qFuzzyCompare(cutRotation, part->second.cutRotation))
return;
part->second.setCutRotation(cutRotation);
part->second.dirty = true;
emit partCutRotationChanged(partId);
emit skeletonChanged();
}
void Document::setPartCutTemplate(QUuid partId, std::vector<QVector2D> cutTemplate)
{
auto part = partMap.find(partId);
if (part == partMap.end()) {
qDebug() << "Part not found:" << partId;
return;
}
if (cutTemplatePointsCompare(cutTemplate, part->second.cutTemplate))
return;
part->second.setCutTemplate(cutTemplate);
part->second.dirty = true;
emit partCutTemplateChanged(partId);
emit skeletonChanged();
}
void Document::setPartColorState(QUuid partId, bool hasColor, QColor color)
{
auto part = partMap.find(partId);

View File

@ -401,6 +401,7 @@ signals:
void partRoundStateChanged(QUuid partId);
void partColorStateChanged(QUuid partId);
void partCutRotationChanged(QUuid partId);
void partCutTemplateChanged(QUuid partId);
void partMaterialIdChanged(QUuid partId);
void componentCombineModeChanged(QUuid componentId);
void cleanup();
@ -554,6 +555,7 @@ public slots:
void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setPartCutRotation(QUuid partId, float cutRotation);
void setPartCutTemplate(QUuid partId, std::vector<QVector2D> cutTemplate);
void setPartMaterialId(QUuid partId, QUuid materialId);
void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
void moveComponentUp(QUuid componentId);

View File

@ -835,8 +835,9 @@ DocumentWindow::DocumentWindow() :
connect(m_document, &Document::partDeformThicknessChanged, partTreeWidget, &PartTreeWidget::partDeformChanged);
connect(m_document, &Document::partDeformWidthChanged, partTreeWidget, &PartTreeWidget::partDeformChanged);
connect(m_document, &Document::partRoundStateChanged, partTreeWidget, &PartTreeWidget::partRoundStateChanged);
connect(m_document, &Document::partCutRotationChanged, partTreeWidget, &PartTreeWidget::partWrapStateChanged);
connect(m_document, &Document::partColorStateChanged, partTreeWidget, &PartTreeWidget::partColorStateChanged);
connect(m_document, &Document::partCutRotationChanged, partTreeWidget, &PartTreeWidget::partCutRotationChanged);
connect(m_document, &Document::partCutTemplateChanged, partTreeWidget, &PartTreeWidget::partCutTemplateChanged);
connect(m_document, &Document::partMaterialIdChanged, partTreeWidget, &PartTreeWidget::partMaterialIdChanged);
connect(m_document, &Document::partRemoved, partTreeWidget, &PartTreeWidget::partRemoved);
connect(m_document, &Document::cleanup, partTreeWidget, &PartTreeWidget::removeAllContent);

View File

@ -10,13 +10,7 @@
#include "meshgenerator.h"
#include "util.h"
#include "trianglesourcenoderesolve.h"
const std::vector<QVector2D> g_defaultCutTemplate = {
{-1.0, -1.0},
{ 1.0, -1.0},
{ 1.0, 1.0},
{-1.0, 1.0},
};
#include "cuttemplate.h"
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
m_snapshot(snapshot)
@ -155,7 +149,8 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
float deformWidth = 1.0;
float cutRotation = 0.0;
std::vector<QVector2D> cutTemplate = g_defaultCutTemplate;
std::vector<QVector2D> cutTemplate = cutTemplatePointsFromString(valueOfKeyInMapOrEmpty(part, "cutTemplate"));
normalizeCutTemplatePoints(&cutTemplate);
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
if (!cutRotationString.isEmpty()) {
cutRotation = cutRotationString.toFloat();

View File

@ -49,7 +49,7 @@ PartTreeWidget::PartTreeWidget(const Document *document, QWidget *parent) :
setFont(m_normalFont);
QRadialGradient gradient(QPointF(0.2, 0.3), 0.3);
QRadialGradient gradient(QPointF(0.115, 0.3), 0.3);
QColor fillColor = QColor(0xfb, 0xf9, 0x87);
fillColor.setAlphaF(0.85);
gradient.setCoordinateMode(QGradient::StretchToDeviceMode);
@ -910,7 +910,18 @@ void PartTreeWidget::partRoundStateChanged(QUuid partId)
widget->updateRoundButton();
}
void PartTreeWidget::partWrapStateChanged(QUuid partId)
void PartTreeWidget::partColorStateChanged(QUuid partId)
{
auto item = m_partItemMap.find(partId);
if (item == m_partItemMap.end()) {
qDebug() << "Part item not found:" << partId;
return;
}
PartWidget *widget = (PartWidget *)itemWidget(item->second, 0);
widget->updateColorButton();
}
void PartTreeWidget::partCutRotationChanged(QUuid partId)
{
auto item = m_partItemMap.find(partId);
if (item == m_partItemMap.end()) {
@ -921,7 +932,7 @@ void PartTreeWidget::partWrapStateChanged(QUuid partId)
widget->updateCutRotationButton();
}
void PartTreeWidget::partColorStateChanged(QUuid partId)
void PartTreeWidget::partCutTemplateChanged(QUuid partId)
{
auto item = m_partItemMap.find(partId);
if (item == m_partItemMap.end()) {
@ -929,7 +940,7 @@ void PartTreeWidget::partColorStateChanged(QUuid partId)
return;
}
PartWidget *widget = (PartWidget *)itemWidget(item->second, 0);
widget->updateColorButton();
widget->updateCutTemplateButton();
}
void PartTreeWidget::partMaterialIdChanged(QUuid partId)

View File

@ -59,8 +59,9 @@ public slots:
void partXmirrorStateChanged(QUuid partId);
void partDeformChanged(QUuid partId);
void partRoundStateChanged(QUuid partId);
void partWrapStateChanged(QUuid partId);
void partColorStateChanged(QUuid partId);
void partCutRotationChanged(QUuid partId);
void partCutTemplateChanged(QUuid partId);
void partMaterialIdChanged(QUuid partId);
void partChecked(QUuid partId);
void partUnchecked(QUuid partId);

View File

@ -6,11 +6,17 @@
#include <QColorDialog>
#include <QSizePolicy>
#include <QFileDialog>
#include <QSizePolicy>
#include "partwidget.h"
#include "theme.h"
#include "floatnumberwidget.h"
#include "materiallistwidget.h"
#include "infolabel.h"
#include "cuttemplate.h"
#include "cutdocument.h"
#include "skeletongraphicswidget.h"
#include "shortcuts.h"
#include "graphicscontainerwidget.h"
PartWidget::PartWidget(const Document *document, QUuid partId) :
m_document(document),
@ -31,7 +37,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
initButton(m_lockButton);
m_subdivButton = new QPushButton();
m_subdivButton->setToolTip(tr("Square/Round"));
m_subdivButton->setToolTip(tr("Subdivide"));
m_subdivButton->setSizePolicy(retainSizePolicy);
initButton(m_subdivButton);
@ -60,10 +66,15 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
m_colorButton->setSizePolicy(retainSizePolicy);
initButton(m_colorButton);
m_cutButton = new QPushButton;
m_cutButton->setToolTip(tr("Rotation"));
m_cutButton->setSizePolicy(retainSizePolicy);
initButton(m_cutButton);
m_cutRotationButton = new QPushButton;
m_cutRotationButton->setToolTip(tr("Cut rotation"));
m_cutRotationButton->setSizePolicy(retainSizePolicy);
initButton(m_cutRotationButton);
m_cutTemplateButton = new QPushButton;
m_cutTemplateButton->setToolTip(tr("Cut template"));
m_cutTemplateButton->setSizePolicy(retainSizePolicy);
initButton(m_cutTemplateButton);
m_previewWidget = new ModelWidget;
m_previewWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
@ -88,6 +99,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
toolsLayout->setContentsMargins(0, 0, 5, 0);
int row = 0;
int col = 0;
toolsLayout->addWidget(m_visibleButton, row, col++, Qt::AlignBottom);
toolsLayout->addWidget(m_lockButton, row, col++, Qt::AlignBottom);
toolsLayout->addWidget(m_disableButton, row, col++, Qt::AlignBottom);
toolsLayout->addWidget(m_xMirrorButton, row, col++, Qt::AlignBottom);
@ -95,16 +107,17 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
row++;
col = 0;
toolsLayout->addWidget(m_subdivButton, row, col++, Qt::AlignTop);
toolsLayout->addWidget(m_cutButton, row, col++, Qt::AlignTop);
toolsLayout->addWidget(m_roundButton, row, col++, Qt::AlignTop);
toolsLayout->addWidget(m_cutTemplateButton, row, col++, Qt::AlignTop);
toolsLayout->addWidget(m_cutRotationButton, row, col++, Qt::AlignTop);
toolsLayout->addWidget(m_deformButton, row, col++, Qt::AlignTop);
m_visibleButton->setContentsMargins(0, 0, 0, 0);
//m_visibleButton->setContentsMargins(0, 0, 0, 0);
QHBoxLayout *previewAndToolsLayout = new QHBoxLayout;
previewAndToolsLayout->setSpacing(0);
previewAndToolsLayout->setContentsMargins(0, 0, 0, 0);
previewAndToolsLayout->addWidget(m_visibleButton);
//previewAndToolsLayout->addWidget(m_visibleButton);
previewAndToolsLayout->addWidget(m_previewWidget);
previewAndToolsLayout->addLayout(toolsLayout);
previewAndToolsLayout->setStretch(0, 0);
@ -144,6 +157,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
connect(this, &PartWidget::setPartDeformWidth, m_document, &Document::setPartDeformWidth);
connect(this, &PartWidget::setPartRoundState, m_document, &Document::setPartRoundState);
connect(this, &PartWidget::setPartCutRotation, m_document, &Document::setPartCutRotation);
connect(this, &PartWidget::setPartCutTemplate, m_document, &Document::setPartCutTemplate);
connect(this, &PartWidget::setPartColorState, m_document, &Document::setPartColorState);
connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId);
connect(this, &PartWidget::checkPart, m_document, &Document::checkPart);
@ -230,7 +244,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
showColorSettingPopup(mapFromGlobal(QCursor::pos()));
});
connect(m_cutButton, &QPushButton::clicked, [=]() {
connect(m_cutRotationButton, &QPushButton::clicked, [=]() {
const SkeletonPart *part = m_document->findPart(m_partId);
if (!part) {
qDebug() << "Part not found:" << m_partId;
@ -239,6 +253,15 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
showCutRotationSettingPopup(mapFromGlobal(QCursor::pos()));
});
connect(m_cutTemplateButton, &QPushButton::clicked, [=]() {
const SkeletonPart *part = m_document->findPart(m_partId);
if (!part) {
qDebug() << "Part not found:" << m_partId;
return;
}
showCutTemplateSettingPopup(mapFromGlobal(QCursor::pos()));
});
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
setFixedSize(preferredSize());
@ -266,6 +289,7 @@ void PartWidget::updateAllButtons()
updateRoundButton();
updateColorButton();
updateCutRotationButton();
updateCutTemplateButton();
}
void PartWidget::updateCheckedState(bool checked)
@ -418,6 +442,92 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
popupMenu.exec(mapToGlobal(pos));
}
void PartWidget::showCutTemplateSettingPopup(const QPoint &pos)
{
QMenu popupMenu;
const SkeletonPart *part = m_document->findPart(m_partId);
if (!part) {
qDebug() << "Find part failed:" << m_partId;
return;
}
CutDocument cutDocument;
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(&cutDocument);
graphicsWidget->setNodePositionModifyOnly(true);
graphicsWidget->setMainProfileOnly(true);
GraphicsContainerWidget *containerWidget = new GraphicsContainerWidget;
containerWidget->setGraphicsWidget(graphicsWidget);
QGridLayout *containerLayout = new QGridLayout;
containerLayout->setSpacing(0);
containerLayout->setContentsMargins(1, 0, 0, 0);
containerLayout->addWidget(graphicsWidget);
containerWidget->setLayout(containerLayout);
containerWidget->setFixedSize(160, 100);
connect(containerWidget, &GraphicsContainerWidget::containerSizeChanged,
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, &cutDocument, &CutDocument::moveNodeBy);
connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, &cutDocument, &CutDocument::setNodeOrigin);
connect(graphicsWidget, &SkeletonGraphicsWidget::groupOperationAdded, &cutDocument, &CutDocument::saveHistoryItem);
connect(graphicsWidget, &SkeletonGraphicsWidget::undo, &cutDocument, &CutDocument::undo);
connect(graphicsWidget, &SkeletonGraphicsWidget::redo, &cutDocument, &CutDocument::redo);
connect(graphicsWidget, &SkeletonGraphicsWidget::paste, &cutDocument, &CutDocument::paste);
connect(&cutDocument, &CutDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent);
connect(&cutDocument, &CutDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
connect(&cutDocument, &CutDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
connect(&cutDocument, &CutDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
cutDocument.fromCutTemplate(part->cutTemplate);
connect(&cutDocument, &CutDocument::cutTemplateChanged, this, [&]() {
std::vector<QVector2D> cutTemplate;
cutDocument.toCutTemplate(cutTemplate);
emit setPartCutTemplate(m_partId, cutTemplate);
});
QWidget *popup = new QWidget;
initShortCuts(popup, graphicsWidget);
std::vector<QPushButton *> presetButtons;
for (size_t i = 0; i < (size_t)CutTemplate::Count; ++i) {
CutTemplate cutTemplate = (CutTemplate)i;
QPushButton *button = new QPushButton(CutTemplateToDispName(cutTemplate));
connect(button, &QPushButton::clicked, [&]() {
auto points = CutTemplateToPoints(cutTemplate);
cutDocument.fromCutTemplate(points);
emit setPartCutTemplate(m_partId, points);
emit groupOperationAdded();
});
Theme::initToolButton(button);
presetButtons.push_back(button);
}
QVBoxLayout *layout = new QVBoxLayout;
QHBoxLayout *presetButtonsLayout = new QHBoxLayout;
for (const auto &it: presetButtons)
presetButtonsLayout->addWidget(it);
presetButtonsLayout->addStretch();
layout->addLayout(presetButtonsLayout);
layout->addWidget(containerWidget);
popup->setLayout(layout);
popup->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
QWidgetAction action(this);
action.setDefaultWidget(popup);
popupMenu.addAction(&action);
popupMenu.exec(mapToGlobal(pos));
}
void PartWidget::showDeformSettingPopup(const QPoint &pos)
{
QMenu popupMenu;
@ -620,9 +730,22 @@ void PartWidget::updateCutRotationButton()
return;
}
if (part->cutRotationAdjusted())
updateButton(m_cutButton, QChar(fa::spinner), true);
updateButton(m_cutRotationButton, QChar(fa::spinner), true);
else
updateButton(m_cutButton, QChar(fa::spinner), false);
updateButton(m_cutRotationButton, QChar(fa::spinner), false);
}
void PartWidget::updateCutTemplateButton()
{
const SkeletonPart *part = m_document->findPart(m_partId);
if (!part) {
qDebug() << "Part not found:" << m_partId;
return;
}
if (part->cutTemplateAdjusted())
updateButton(m_cutTemplateButton, QChar(fa::objectungroup), true);
else
updateButton(m_cutTemplateButton, QChar(fa::objectungroup), false);
}
void PartWidget::reload()

View File

@ -21,6 +21,7 @@ signals:
void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color);
void setPartCutRotation(QUuid partId, float cutRotation);
void setPartCutTemplate(QUuid partId, std::vector<QVector2D> cutTemplate);
void setPartMaterialId(QUuid partId, QUuid materialId);
void movePartUp(QUuid partId);
void movePartDown(QUuid partId);
@ -44,6 +45,7 @@ public:
void updateRoundButton();
void updateColorButton();
void updateCutRotationButton();
void updateCutTemplateButton();
void updateCheckedState(bool checked);
void updateUnnormalState(bool unnormal);
static QSize preferredSize();
@ -53,6 +55,7 @@ protected:
public slots:
void showDeformSettingPopup(const QPoint &pos);
void showCutRotationSettingPopup(const QPoint &pos);
void showCutTemplateSettingPopup(const QPoint &pos);
void showColorSettingPopup(const QPoint &pos);
private: // need initialize
const Document *m_document;
@ -69,7 +72,8 @@ private:
QPushButton *m_deformButton;
QPushButton *m_roundButton;
QPushButton *m_colorButton;
QPushButton *m_cutButton;
QPushButton *m_cutRotationButton;
QPushButton *m_cutTemplateButton;
QWidget *m_backgroundWidget;
private:
void initToolButton(QPushButton *button);

View File

@ -9,6 +9,7 @@
#include "bonemark.h"
#include "theme.h"
#include "meshloader.h"
#include "cuttemplate.h"
class SkeletonNode
{
@ -84,6 +85,8 @@ public:
std::vector<QUuid> nodeIds;
bool dirty;
float cutRotation;
std::vector<QVector2D> cutTemplate;
bool cutTemplateChanged;
QUuid materialId;
SkeletonPart(const QUuid &withId=QUuid()) :
visible(true),
@ -98,7 +101,9 @@ public:
color(Theme::white),
hasColor(false),
dirty(true),
cutRotation(0.0)
cutRotation(0.0),
cutTemplate(CutTemplateToPoints(CutTemplate::Quad)),
cutTemplateChanged(false)
{
id = withId.isNull() ? QUuid::createUuid() : withId;
}
@ -126,6 +131,11 @@ public:
toRotation = 1;
cutRotation = toRotation;
}
void setCutTemplate(std::vector<QVector2D> points)
{
cutTemplate = points;
cutTemplateChanged = true;
}
bool deformThicknessAdjusted() const
{
return fabs(deformThickness - 1.0) >= 0.01;
@ -142,6 +152,10 @@ public:
{
return fabs(cutRotation - 0.0) >= 0.01;
}
bool cutTemplateAdjusted() const
{
return cutTemplateChanged;
}
bool materialAdjusted() const
{
return !materialId.isNull();
@ -164,6 +178,8 @@ public:
color = other.color;
hasColor = other.hasColor;
cutRotation = other.cutRotation;
cutTemplate = other.cutTemplate;
cutTemplateChanged = other.cutTemplateChanged;
componentId = other.componentId;
dirty = other.dirty;
materialId = other.materialId;

View File

@ -47,6 +47,7 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
m_inTempDragMode(false),
m_modeBeforeEnterTempDragMode(SkeletonDocumentEditMode::Select),
m_nodePositionModifyOnly(false),
m_mainProfileOnly(false),
m_turnaroundOpacity(0.25)
{
setRenderHint(QPainter::Antialiasing, false);
@ -171,7 +172,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
}
QAction copyAction(tr("Copy"), this);
if (hasNodeSelection()) {
if (!m_mainProfileOnly && hasNodeSelection()) {
connect(&copyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
contextMenu.addAction(&copyAction);
}
@ -213,7 +214,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
}
QAction switchChainSideAction(tr("Switch Chain Side"), this);
if (m_nodePositionModifyOnly && hasNodeSelection()) {
if (m_nodePositionModifyOnly && !m_mainProfileOnly && hasNodeSelection()) {
connect(&switchChainSideAction, &QAction::triggered, this, &SkeletonGraphicsWidget::switchSelectedChainSide);
contextMenu.addAction(&switchChainSideAction);
}
@ -1747,6 +1748,8 @@ void SkeletonGraphicsWidget::nodeAdded(QUuid nodeId)
sideProfileItem->setMarkColor(markColor);
mainProfileItem->setId(nodeId);
sideProfileItem->setId(nodeId);
if (m_mainProfileOnly)
sideProfileItem->hide();
scene()->addItem(mainProfileItem);
scene()->addItem(sideProfileItem);
nodeItemMap[nodeId] = std::make_pair(mainProfileItem, sideProfileItem);
@ -1796,6 +1799,8 @@ void SkeletonGraphicsWidget::edgeAdded(QUuid edgeId)
sideProfileEdgeItem->setId(edgeId);
mainProfileEdgeItem->setEndpoints(fromIt->second.first, toIt->second.first);
sideProfileEdgeItem->setEndpoints(fromIt->second.second, toIt->second.second);
if (m_mainProfileOnly)
sideProfileEdgeItem->hide();
scene()->addItem(mainProfileEdgeItem);
scene()->addItem(sideProfileEdgeItem);
edgeItemMap[edgeId] = std::make_pair(mainProfileEdgeItem, sideProfileEdgeItem);
@ -2554,3 +2559,10 @@ void SkeletonGraphicsWidget::setNodePositionModifyOnly(bool nodePositionModifyOn
{
m_nodePositionModifyOnly = nodePositionModifyOnly;
}
void SkeletonGraphicsWidget::setMainProfileOnly(bool mainProfileOnly)
{
m_mainProfileOnly = mainProfileOnly;
}

View File

@ -411,6 +411,7 @@ public:
bool hasTwoDisconnectedNodesSelection();
void setModelWidget(ModelWidget *modelWidget);
void setNodePositionModifyOnly(bool nodePositionModifyOnly);
void setMainProfileOnly(bool mainProfileOnly);
bool inputWheelEventFromOtherWidget(QWheelEvent *event);
protected:
void mouseMoveEvent(QMouseEvent *event) override;
@ -564,6 +565,7 @@ private: //need initalize
bool m_inTempDragMode;
SkeletonDocumentEditMode m_modeBeforeEnterTempDragMode;
bool m_nodePositionModifyOnly;
bool m_mainProfileOnly;
float m_turnaroundOpacity;
private:
QVector3D m_ikMoveTarget;

View File

@ -164,6 +164,17 @@ void Theme::initAwesomeToolButton(QPushButton *button)
Theme::initAwesomeToolButtonWithoutFont(button);
}
void Theme::initToolButton(QPushButton *button)
{
QFont font = button->font();
font.setWeight(QFont::Light);
font.setBold(false);
button->setFont(font);
button->setFixedHeight(Theme::toolIconSize * 0.75);
button->setStyleSheet("QPushButton {color: #f7d9c8}");
button->setFocusPolicy(Qt::NoFocus);
}
QWidget *Theme::createHorizontalLineWidget()
{
QWidget *hrLightWidget = new QWidget;

View File

@ -51,6 +51,7 @@ public:
static void initAwesomeToolButton(QPushButton *button);
static void initAwesomeToolButtonWithoutFont(QPushButton *button);
static void initAwsomeBaseSizes();
static void initToolButton(QPushButton *button);
};
#endif

View File

@ -574,11 +574,18 @@ void Builder::makeCut(const QVector3D &position,
}
}
baseNormal = orientedBaseNormal.normalized();
QVector3D u = QVector3D::crossProduct(cutNormal, orientedBaseNormal).normalized();
QVector3D v = QVector3D::crossProduct(u, cutNormal).normalized();
auto finalCutTemplate = cutTemplate;
auto finalCutNormal = cutNormal;
if (QVector3D::dotProduct(cutNormal, traverseDirection) <= 0) {
baseNormal = -baseNormal;
finalCutNormal = -finalCutNormal;
std::reverse(finalCutTemplate.begin(), finalCutTemplate.end());
}
QVector3D u = QVector3D::crossProduct(finalCutNormal, baseNormal).normalized();
QVector3D v = QVector3D::crossProduct(u, finalCutNormal).normalized();
auto uFactor = u * radius;
auto vFactor = v * radius;
for (const auto &t: cutTemplate) {
for (const auto &t: finalCutTemplate) {
resultCut.push_back(uFactor * t.x() + vFactor * t.y());
}
if (!qFuzzyIsNull(m_cutRotation)) {