diff --git a/dust3d.pro b/dust3d.pro index 00dc941c..1f69bd04 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -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 diff --git a/src/cutdocument.cpp b/src/cutdocument.cpp new file mode 100644 index 00000000..a42c251e --- /dev/null +++ b/src/cutdocument.cpp @@ -0,0 +1,201 @@ +#include +#include +#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 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 &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 &cutTemplate) +{ + reset(); + + std::set newAddedNodeIds; + std::set 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(); +} + diff --git a/src/cutdocument.h b/src/cutdocument.h new file mode 100644 index 00000000..be34a2b5 --- /dev/null +++ b/src/cutdocument.h @@ -0,0 +1,60 @@ +#ifndef DUST3D_CUT_DOCUMENT_H +#define DUST3D_CUT_DOCUMENT_H +#include +#include +#include "skeletondocument.h" + +struct CutHistoryItem +{ + std::vector 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 nodeIdSet) const override; + + void reset(); + void toCutTemplate(std::vector &cutTemplate); + void fromCutTemplate(const std::vector &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 m_undoItems; + std::deque m_redoItems; + std::vector m_cutNodeIds; + QUuid m_partId; + + QVector2D nodeToCutPosition(const QVector3D &nodePosition); + QVector3D cutToNodePosition(const QVector2D &cutPosition); +}; + +#endif + diff --git a/src/cuttemplate.cpp b/src/cuttemplate.cpp new file mode 100644 index 00000000..bb91f055 --- /dev/null +++ b/src/cuttemplate.cpp @@ -0,0 +1,83 @@ +#include +#include "cuttemplate.h" + +IMPL_CutTemplateToDispName +TMPL_CutTemplateToPoints + +static const auto g_defaultCutTemplate = CutTemplateToPoints(CutTemplate::Quad); + +bool cutTemplatePointsCompare(const std::vector &first, const std::vector &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 &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 cutTemplatePointsFromString(const QString &pointsString) +{ + std::vector 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 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 *points) +{ + QVector2D center; + if (nullptr == points || points->empty()) + return; + float xLow = std::numeric_limits::max(); + float xHigh = std::numeric_limits::lowest(); + float yLow = std::numeric_limits::max(); + float yHigh = std::numeric_limits::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); + } +} diff --git a/src/cuttemplate.h b/src/cuttemplate.h new file mode 100644 index 00000000..2fbfabf1 --- /dev/null +++ b/src/cuttemplate.h @@ -0,0 +1,53 @@ +#ifndef CUT_TEMPLATE_H +#define CUT_TEMPLATE_H +#include +#include +#include +#include + +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 CutTemplateToPoints(CutTemplate cutTemplate); +#define TMPL_CutTemplateToPoints \ +std::vector 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 &first, const std::vector &second); +QString cutTemplatePointsToString(const std::vector &points); +std::vector cutTemplatePointsFromString(const QString &pointsString); +void normalizeCutTemplatePoints(std::vector *points); + +#endif diff --git a/src/document.cpp b/src/document.cpp index 4d75fe07..6dc21076 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -861,6 +861,8 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &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 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); diff --git a/src/document.h b/src/document.h index c57de4de..99d98ac9 100644 --- a/src/document.h +++ b/src/document.h @@ -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 cutTemplate); void setPartMaterialId(QUuid partId, QUuid materialId); void setComponentCombineMode(QUuid componentId, CombineMode combineMode); void moveComponentUp(QUuid componentId); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 2364c724..7a8ba3a0 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -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); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 2756c682..18a87847 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -10,13 +10,7 @@ #include "meshgenerator.h" #include "util.h" #include "trianglesourcenoderesolve.h" - -const std::vector 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 cutTemplate = g_defaultCutTemplate; + std::vector cutTemplate = cutTemplatePointsFromString(valueOfKeyInMapOrEmpty(part, "cutTemplate")); + normalizeCutTemplatePoints(&cutTemplate); QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation"); if (!cutRotationString.isEmpty()) { cutRotation = cutRotationString.toFloat(); diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index d666ece3..b31cff59 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -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) diff --git a/src/parttreewidget.h b/src/parttreewidget.h index 7471b97a..c5068167 100644 --- a/src/parttreewidget.h +++ b/src/parttreewidget.h @@ -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); diff --git a/src/partwidget.cpp b/src/partwidget.cpp index cd42de3d..1eddaf2e 100644 --- a/src/partwidget.cpp +++ b/src/partwidget.cpp @@ -6,11 +6,17 @@ #include #include #include +#include #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 cutTemplate; + cutDocument.toCutTemplate(cutTemplate); + emit setPartCutTemplate(m_partId, cutTemplate); + }); + + QWidget *popup = new QWidget; + + initShortCuts(popup, graphicsWidget); + + std::vector 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() diff --git a/src/partwidget.h b/src/partwidget.h index b51c419e..955dcb55 100644 --- a/src/partwidget.h +++ b/src/partwidget.h @@ -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 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); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index b1339f56..e37db897 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -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 nodeIds; bool dirty; float cutRotation; + std::vector 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 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; diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index fbfd8c2a..f8b1c4ae 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -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(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy); contextMenu.addAction(©Action); } @@ -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; +} + + diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index 82ae4788..ce683a66 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -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; diff --git a/src/theme.cpp b/src/theme.cpp index 65a29a50..09fe58d2 100644 --- a/src/theme.cpp +++ b/src/theme.cpp @@ -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; diff --git a/src/theme.h b/src/theme.h index b8a32433..c5094e04 100644 --- a/src/theme.h +++ b/src/theme.h @@ -51,6 +51,7 @@ public: static void initAwesomeToolButton(QPushButton *button); static void initAwesomeToolButtonWithoutFont(QPushButton *button); static void initAwsomeBaseSizes(); + static void initToolButton(QPushButton *button); }; #endif diff --git a/thirdparty/nodemesh/nodemesh/builder.cpp b/thirdparty/nodemesh/nodemesh/builder.cpp index 1dce9562..7f998e87 100644 --- a/thirdparty/nodemesh/nodemesh/builder.cpp +++ b/thirdparty/nodemesh/nodemesh/builder.cpp @@ -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)) {