diff --git a/dust3d.pro b/dust3d.pro index eb9e865e..1eeef3f4 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -37,12 +37,9 @@ wd = $$replace(PWD, /, $$QMAKE_DIR_SEP) # Update the .ts file from source qtPrepareTool(LUPDATE, lupdate) LUPDATE += src/*.cpp src/*.h -locations none -TSFILES = $$files($$PWD/languages/dust3d_??.ts) -for(file, TSFILES) { - lang = $$replace(file, .*_([^/]*).ts, 1) - v = ts-$${lang}.commands - $$v = cd $$wd && $$LUPDATE $$SOURCES $$APP_FILES -ts $$file - QMAKE_EXTRA_TARGETS += ts-$$lang +for(lang, LANGUAGES) { + command = $$LUPDATE -ts languages/dust3d_$${lang}.ts + system($$command)|error("Failed to run: $$command") } ########################################################## diff --git a/languages/dust3d_zh_CN.ts b/languages/dust3d_zh_CN.ts index 47c174bd..3e4fe159 100644 --- a/languages/dust3d_zh_CN.ts +++ b/languages/dust3d_zh_CN.ts @@ -342,6 +342,18 @@ Tips: glTF Binary Format (.glb) glb文档(.glb) + + Rotation + 旋转 + + + Cut Face... + 切面... + + + Clear Cut Face + 清除切面 + ExportPreviewWidget @@ -1075,5 +1087,13 @@ Tips: Unselect All 取消全选 + + Cut Face... + 切面... + + + Clear Cut Face + 清除切面 + diff --git a/src/document.cpp b/src/document.cpp index a659850f..e1afd4ce 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -614,6 +614,19 @@ void Document::updateLinkedPart(QUuid oldPartId, QUuid newPartId) partIt.second.setCutFaceLinkedId(newPartId); } } + std::set dirtyPartIds; + for (auto &nodeIt: nodeMap) { + if (nodeIt.second.cutFaceLinkedId == oldPartId) { + dirtyPartIds.insert(nodeIt.second.partId); + nodeIt.second.setCutFaceLinkedId(newPartId); + } + } + for (const auto &partId: dirtyPartIds) { + SkeletonPart *part = (SkeletonPart *)findPart(partId); + if (nullptr == part) + continue; + part->dirty = true; + } } const Component *Document::findComponent(QUuid componentId) const @@ -799,6 +812,75 @@ void Document::setNodeBoneMark(QUuid nodeId, BoneMark mark) emit skeletonChanged(); } +void Document::setNodeCutRotation(QUuid nodeId, float cutRotation) +{ + auto node = nodeMap.find(nodeId); + if (node == nodeMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + if (qFuzzyCompare(cutRotation, node->second.cutRotation)) + return; + node->second.setCutRotation(cutRotation); + auto part = partMap.find(node->second.partId); + if (part != partMap.end()) + part->second.dirty = true; + emit nodeCutRotationChanged(nodeId); + emit skeletonChanged(); +} + +void Document::setNodeCutFace(QUuid nodeId, CutFace cutFace) +{ + auto node = nodeMap.find(nodeId); + if (node == nodeMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + if (node->second.cutFace == cutFace) + return; + node->second.setCutFace(cutFace); + auto part = partMap.find(node->second.partId); + if (part != partMap.end()) + part->second.dirty = true; + emit nodeCutFaceChanged(nodeId); + emit skeletonChanged(); +} + +void Document::setNodeCutFaceLinkedId(QUuid nodeId, QUuid linkedId) +{ + auto node = nodeMap.find(nodeId); + if (node == nodeMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + if (node->second.cutFace == CutFace::UserDefined && + node->second.cutFaceLinkedId == linkedId) + return; + node->second.setCutFaceLinkedId(linkedId); + auto part = partMap.find(node->second.partId); + if (part != partMap.end()) + part->second.dirty = true; + emit nodeCutFaceChanged(nodeId); + emit skeletonChanged(); +} + +void Document::clearNodeCutFaceSettings(QUuid nodeId) +{ + auto node = nodeMap.find(nodeId); + if (node == nodeMap.end()) { + qDebug() << "Node not found:" << nodeId; + return; + } + if (!node->second.hasCutFaceSettings) + return; + node->second.clearCutFaceSettings(); + auto part = partMap.find(node->second.partId); + if (part != partMap.end()) + part->second.dirty = true; + emit nodeCutFaceChanged(nodeId); + emit skeletonChanged(); +} + void Document::updateTurnaround(const QImage &image) { turnaround = image; @@ -976,6 +1058,16 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId node["partId"] = nodeIt.second.partId.toString(); if (nodeIt.second.boneMark != BoneMark::None) node["boneMark"] = BoneMarkToString(nodeIt.second.boneMark); + if (nodeIt.second.hasCutFaceSettings) { + node["cutRotation"] = QString::number(nodeIt.second.cutRotation); + if (CutFace::UserDefined == nodeIt.second.cutFace) { + if (!nodeIt.second.cutFaceLinkedId.isNull()) { + node["cutFace"] = nodeIt.second.cutFaceLinkedId.toString(); + } + } else { + node["cutFace"] = CutFaceToString(nodeIt.second.cutFace); + } + } if (!nodeIt.second.name.isEmpty()) node["name"] = nodeIt.second.name; snapshot->nodes[node["id"]] = node; @@ -1248,7 +1340,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) for (const auto &it: cutFaceLinkedIdModifyMap) { SkeletonPart &part = partMap[it.first]; auto findNewLinkedId = oldNewIdMap.find(it.second); - if (oldNewIdMap.find(it.second) == oldNewIdMap.end()) { + if (findNewLinkedId == oldNewIdMap.end()) { if (partMap.find(it.second) == partMap.end()) { part.setCutFaceLinkedId(QUuid()); } @@ -1273,6 +1365,26 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) node.z = valueOfKeyInMapOrEmpty(nodeKv.second, "z").toFloat(); node.partId = oldNewIdMap[QUuid(valueOfKeyInMapOrEmpty(nodeKv.second, "partId"))]; node.boneMark = BoneMarkFromString(valueOfKeyInMapOrEmpty(nodeKv.second, "boneMark").toUtf8().constData()); + const auto &cutRotationIt = nodeKv.second.find("cutRotation"); + if (cutRotationIt != nodeKv.second.end()) + node.setCutRotation(cutRotationIt->second.toFloat()); + const auto &cutFaceIt = nodeKv.second.find("cutFace"); + if (cutFaceIt != nodeKv.second.end()) { + QUuid cutFaceLinkedId = QUuid(cutFaceIt->second); + if (cutFaceLinkedId.isNull()) { + node.setCutFace(CutFaceFromString(cutFaceIt->second.toUtf8().constData())); + } else { + node.setCutFaceLinkedId(cutFaceLinkedId); + auto findNewLinkedId = oldNewIdMap.find(cutFaceLinkedId); + if (findNewLinkedId == oldNewIdMap.end()) { + if (partMap.find(cutFaceLinkedId) == partMap.end()) { + node.setCutFaceLinkedId(QUuid()); + } + } else { + node.setCutFaceLinkedId(findNewLinkedId->second); + } + } + } nodeMap[node.id] = node; newAddedNodeIds.insert(node.id); } diff --git a/src/document.h b/src/document.h index 3cdeffd8..89fe8a66 100644 --- a/src/document.h +++ b/src/document.h @@ -383,6 +383,9 @@ signals: void edgeRemoved(QUuid edgeId); void nodeRadiusChanged(QUuid nodeId); void nodeBoneMarkChanged(QUuid nodeId); + void nodeColorStateChanged(QUuid nodeId); + void nodeCutRotationChanged(QUuid nodeId); + void nodeCutFaceChanged(QUuid nodeId); void nodeOriginChanged(QUuid nodeId); void edgeChanged(QUuid edgeId); void partPreviewChanged(QUuid partId); @@ -536,6 +539,10 @@ public slots: void setNodeOrigin(QUuid nodeId, float x, float y, float z); void setNodeRadius(QUuid nodeId, float radius); void setNodeBoneMark(QUuid nodeId, BoneMark mark); + void setNodeCutRotation(QUuid nodeId, float cutRotation); + void setNodeCutFace(QUuid nodeId, CutFace cutFace); + void setNodeCutFaceLinkedId(QUuid nodeId, QUuid linkedId); + void clearNodeCutFaceSettings(QUuid nodeId); void switchNodeXZ(QUuid nodeId); void moveOriginBy(float x, float y, float z); void addEdge(QUuid fromNodeId, QUuid toNodeId); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 846d0906..9a6dd9dc 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "documentwindow.h" #include "skeletongraphicswidget.h" #include "theme.h" @@ -36,6 +37,8 @@ #include "spinnableawesomebutton.h" #include "fbxfile.h" #include "shortcuts.h" +#include "floatnumberwidget.h" +#include "cutfacelistwidget.h" int DocumentWindow::m_modelRenderWidgetInitialX = 16; int DocumentWindow::m_modelRenderWidgetInitialY = 16; @@ -492,6 +495,18 @@ DocumentWindow::DocumentWindow() : m_graphicsWidget->switchSelectedXZ(); }); m_editMenu->addAction(m_switchXzAction); + + m_setCutFaceAction = new QAction(tr("Cut Face..."), this); + connect(m_setCutFaceAction, &QAction::triggered, [=] { + m_graphicsWidget->showSelectedCutFaceSettingPopup(m_graphicsWidget->mapFromGlobal(QCursor::pos())); + }); + m_editMenu->addAction(m_setCutFaceAction); + + m_clearCutFaceAction = new QAction(tr("Clear Cut Face"), this); + connect(m_clearCutFaceAction, &QAction::triggered, [=] { + m_graphicsWidget->clearSelectedCutFace(); + }); + m_editMenu->addAction(m_clearCutFaceAction); m_alignToMenu = new QMenu(tr("Align To")); @@ -568,6 +583,8 @@ DocumentWindow::DocumentWindow() : m_rotateClockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); m_rotateCounterclockwiseAction->setEnabled(m_graphicsWidget->hasMultipleSelection()); m_switchXzAction->setEnabled(m_graphicsWidget->hasSelection()); + m_setCutFaceAction->setEnabled(m_graphicsWidget->hasSelection()); + m_clearCutFaceAction->setEnabled(m_graphicsWidget->hasCutFaceAdjustedNodesSelection()); m_alignToGlobalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); m_alignToGlobalVerticalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); m_alignToGlobalHorizontalCenterAction->setEnabled(m_graphicsWidget->hasSelection() && m_document->originSettled()); @@ -768,6 +785,7 @@ DocumentWindow::DocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::moveNodeBy, m_document, &Document::moveNodeBy); connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeOrigin, m_document, &Document::setNodeOrigin); connect(graphicsWidget, &SkeletonGraphicsWidget::setNodeBoneMark, m_document, &Document::setNodeBoneMark); + connect(graphicsWidget, &SkeletonGraphicsWidget::clearNodeCutFaceSettings, m_document, &Document::clearNodeCutFaceSettings); connect(graphicsWidget, &SkeletonGraphicsWidget::removeNode, m_document, &Document::removeNode); connect(graphicsWidget, &SkeletonGraphicsWidget::setEditMode, m_document, &Document::setEditMode); connect(graphicsWidget, &SkeletonGraphicsWidget::removeEdge, m_document, &Document::removeEdge); @@ -802,6 +820,7 @@ DocumentWindow::DocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &DocumentWindow::changeTurnaround); connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &DocumentWindow::save); connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &DocumentWindow::open); + connect(graphicsWidget, &SkeletonGraphicsWidget::showCutFaceSettingPopup, this, &DocumentWindow::showCutFaceSettingPopup); connect(m_document, &Document::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded); connect(m_document, &Document::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved); @@ -1494,3 +1513,133 @@ void DocumentWindow::unregisterDialog(QWidget *widget) { m_dialogs.erase(std::remove(m_dialogs.begin(), m_dialogs.end(), widget), m_dialogs.end()); } + +void DocumentWindow::showCutFaceSettingPopup(const QPoint &globalPos, std::set nodeIds) +{ + QMenu popupMenu; + + const SkeletonNode *node = nullptr; + if (1 == nodeIds.size()) { + node = m_document->findNode(*nodeIds.begin()); + } + + QWidget *popup = new QWidget; + + FloatNumberWidget *rotationWidget = new FloatNumberWidget; + rotationWidget->setItemName(tr("Rotation")); + rotationWidget->setRange(-1, 1); + rotationWidget->setValue(0); + if (nullptr != node) { + rotationWidget->setValue(node->cutRotation); + } + + connect(rotationWidget, &FloatNumberWidget::valueChanged, [=](float value) { + m_document->batchChangeBegin(); + for (const auto &id: nodeIds) { + m_document->setNodeCutRotation(id, value); + } + m_document->batchChangeEnd(); + m_document->saveSnapshot(); + }); + + QPushButton *rotationEraser = new QPushButton(QChar(fa::eraser)); + Theme::initAwesomeToolButton(rotationEraser); + + connect(rotationEraser, &QPushButton::clicked, [=]() { + rotationWidget->setValue(0.0); + m_document->saveSnapshot(); + }); + + QHBoxLayout *rotationLayout = new QHBoxLayout; + rotationLayout->addWidget(rotationEraser); + rotationLayout->addWidget(rotationWidget); + + QHBoxLayout *standardFacesLayout = new QHBoxLayout; + QPushButton *buttons[(int)CutFace::Count] = {0}; + + CutFaceListWidget *cutFaceListWidget = new CutFaceListWidget(m_document); + size_t cutFaceTypeCount = (size_t)CutFace::Count; + if (cutFaceListWidget->isEmpty()) + cutFaceTypeCount = (size_t)CutFace::UserDefined; + + auto updateCutFaceButtonState = [&](size_t index) { + for (size_t i = 0; i < (size_t)cutFaceTypeCount; ++i) { + auto button = buttons[i]; + if (i == index) { + button->setFlat(true); + button->setEnabled(false); + } else { + button->setFlat(false); + button->setEnabled(true); + } + } + if (index != (int)CutFace::UserDefined) + cutFaceListWidget->selectCutFace(QUuid()); + }; + + cutFaceListWidget->enableMultipleSelection(false); + if (nullptr != node) { + cutFaceListWidget->selectCutFace(node->cutFaceLinkedId); + } + connect(cutFaceListWidget, &CutFaceListWidget::currentSelectedCutFaceChanged, this, [=](QUuid partId) { + if (partId.isNull()) { + CutFace cutFace = CutFace::Quad; + updateCutFaceButtonState((int)cutFace); + m_document->batchChangeBegin(); + for (const auto &id: nodeIds) { + m_document->setNodeCutFace(id, cutFace); + } + m_document->batchChangeEnd(); + m_document->saveSnapshot(); + } else { + updateCutFaceButtonState((int)CutFace::UserDefined); + m_document->batchChangeBegin(); + for (const auto &id: nodeIds) { + m_document->setNodeCutFaceLinkedId(id, partId); + } + m_document->batchChangeEnd(); + m_document->saveSnapshot(); + } + }); + if (cutFaceListWidget->isEmpty()) + cutFaceListWidget->hide(); + + for (size_t i = 0; i < (size_t)cutFaceTypeCount; ++i) { + CutFace cutFace = (CutFace)i; + QString iconFilename = ":/resources/" + CutFaceToString(cutFace).toLower() + ".png"; + QPixmap pixmap(iconFilename); + QIcon buttonIcon(pixmap); + QPushButton *button = new QPushButton; + button->setIconSize(QSize(Theme::toolIconSize / 2, Theme::toolIconSize / 2)); + button->setIcon(buttonIcon); + connect(button, &QPushButton::clicked, [=]() { + updateCutFaceButtonState(i); + m_document->batchChangeBegin(); + for (const auto &id: nodeIds) { + m_document->setNodeCutFace(id, cutFace); + } + m_document->batchChangeEnd(); + m_document->saveSnapshot(); + }); + standardFacesLayout->addWidget(button); + buttons[i] = button; + } + if (nullptr != node) { + updateCutFaceButtonState((size_t)node->cutFace); + } + + QVBoxLayout *popupLayout = new QVBoxLayout; + popupLayout->addLayout(rotationLayout); + popupLayout->addSpacing(10); + popupLayout->addLayout(standardFacesLayout); + popupLayout->addWidget(cutFaceListWidget); + + popup->setLayout(popupLayout); + + QWidgetAction action(this); + action.setDefaultWidget(popup); + + popupMenu.addAction(&action); + + popupMenu.exec(globalPos); +} diff --git a/src/documentwindow.h b/src/documentwindow.h index 5a83ef54..360b0377 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -67,6 +67,7 @@ public slots: void registerDialog(QWidget *widget); void unregisterDialog(QWidget *widget); void showPreferences(); + void showCutFaceSettingPopup(const QPoint &globalPos, std::set nodeIds); private: void initLockButton(QPushButton *button); void setCurrentFilename(const QString &filename); @@ -119,6 +120,8 @@ private: QAction *m_rotateClockwiseAction; QAction *m_rotateCounterclockwiseAction; QAction *m_switchXzAction; + QAction *m_setCutFaceAction; + QAction *m_clearCutFaceAction; QMenu *m_alignToMenu; QAction *m_alignToGlobalCenterAction; diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index a9705a52..ec5bca8a 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -111,9 +111,24 @@ bool MeshGenerator::checkIsPartDependencyDirty(const QString &partIdString) } QString cutFaceString = valueOfKeyInMapOrEmpty(findPart->second, "cutFace"); QUuid cutFaceLinkedPartId = QUuid(cutFaceString); - if (cutFaceLinkedPartId.isNull()) - return false; - return checkIsPartDirty(cutFaceString); + if (!cutFaceLinkedPartId.isNull()) { + if (checkIsPartDirty(cutFaceString)) + return true; + } + for (const auto &nodeIdString: m_partNodeIds[partIdString]) { + auto findNode = m_snapshot->nodes.find(nodeIdString); + if (findNode == m_snapshot->nodes.end()) { + qDebug() << "Find node failed:" << nodeIdString; + continue; + } + QString cutFaceString = valueOfKeyInMapOrEmpty(findNode->second, "cutFace"); + QUuid cutFaceLinkedPartId = QUuid(cutFaceString); + if (!cutFaceLinkedPartId.isNull()) { + if (checkIsPartDirty(cutFaceString)) + return true; + } + } + return false; } bool MeshGenerator::checkIsComponentDirty(const QString &componentIdString) @@ -167,32 +182,9 @@ void MeshGenerator::checkDirtyFlags() checkIsComponentDirty(QUuid().toString()); } -nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString) +void MeshGenerator::cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector &cutTemplate) { - auto findPart = m_snapshot->parts.find(partIdString); - if (findPart == m_snapshot->parts.end()) { - qDebug() << "Find part failed:" << partIdString; - return nullptr; - } - - QUuid partId = QUuid(partIdString); - auto &part = findPart->second; - bool isDisabled = isTrueValueString(valueOfKeyInMapOrEmpty(part, "disabled")); - bool xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part, "xMirrored")); - bool subdived = isTrueValueString(valueOfKeyInMapOrEmpty(part, "subdived")); - bool rounded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "rounded")); - bool chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(part, "chamfered")); - QString colorString = valueOfKeyInMapOrEmpty(part, "color"); - QColor partColor = colorString.isEmpty() ? m_defaultPartColor : QColor(colorString); - float deformThickness = 1.0; - float deformWidth = 1.0; - float cutRotation = 0.0; - auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData()); - auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData()); - - std::map cutTemplateMapByName; - std::vector cutTemplate; - QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace"); + //std::map cutTemplateMapByName; QUuid cutFaceLinkedPartId = QUuid(cutFaceString); if (!cutFaceLinkedPartId.isNull()) { std::map> cutFaceNodeMap; @@ -306,19 +298,47 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt // Fetch points from linked nodes std::vector cutTemplateNames; cutFacePointsFromNodes(cutTemplate, cutFaceNodes, isRing, &cutTemplateNames); - for (size_t i = 0; i < cutTemplateNames.size(); ++i) { - cutTemplateMapByName.insert({cutTemplateNames[i], cutTemplate[i]}); - } + //for (size_t i = 0; i < cutTemplateNames.size(); ++i) { + // cutTemplateMapByName.insert({cutTemplateNames[i], cutTemplate[i]}); + //} } } if (cutTemplate.size() < 3) { CutFace cutFace = CutFaceFromString(cutFaceString.toUtf8().constData()); cutTemplate = CutFaceToPoints(cutFace); - cutTemplateMapByName.clear(); - for (size_t i = 0; i < cutTemplate.size(); ++i) { - cutTemplateMapByName.insert({cutFaceString + "/" + QString::number(i + 1), cutTemplate[i]}); - } + //cutTemplateMapByName.clear(); + //for (size_t i = 0; i < cutTemplate.size(); ++i) { + // cutTemplateMapByName.insert({cutFaceString + "/" + QString::number(i + 1), cutTemplate[i]}); + //} } +} + +nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdString) +{ + auto findPart = m_snapshot->parts.find(partIdString); + if (findPart == m_snapshot->parts.end()) { + qDebug() << "Find part failed:" << partIdString; + return nullptr; + } + + QUuid partId = QUuid(partIdString); + auto &part = findPart->second; + bool isDisabled = isTrueValueString(valueOfKeyInMapOrEmpty(part, "disabled")); + bool xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part, "xMirrored")); + bool subdived = isTrueValueString(valueOfKeyInMapOrEmpty(part, "subdived")); + bool rounded = isTrueValueString(valueOfKeyInMapOrEmpty(part, "rounded")); + bool chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(part, "chamfered")); + QString colorString = valueOfKeyInMapOrEmpty(part, "color"); + QColor partColor = colorString.isEmpty() ? m_defaultPartColor : QColor(colorString); + float deformThickness = 1.0; + float deformWidth = 1.0; + float cutRotation = 0.0; + auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData()); + auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData()); + + QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace"); + std::vector cutTemplate; + cutFaceStringToCutTemplate(cutFaceString, cutTemplate); if (chamfered) nodemesh::chamferFace2D(&cutTemplate); @@ -362,6 +382,9 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt float radius = 0; QVector3D position; BoneMark boneMark = BoneMark::None; + bool hasCutFaceSettings = false; + float cutRotation = 0.0; + QString cutFace; }; std::map nodeInfos; for (const auto &nodeIdString: m_partNodeIds[partIdString]) { @@ -379,10 +402,27 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt BoneMark boneMark = BoneMarkFromString(valueOfKeyInMapOrEmpty(node, "boneMark").toUtf8().constData()); + bool hasCutFaceSettings = false; + float cutRotation = 0.0; + QString cutFace; + + const auto &cutFaceIt = node.find("cutFace"); + if (cutFaceIt != node.end()) { + cutFace = cutFaceIt->second; + hasCutFaceSettings = true; + const auto &cutRotationIt = node.find("cutRotation"); + if (cutRotationIt != node.end()) { + cutRotation = cutRotationIt->second.toFloat(); + } + } + auto &nodeInfo = nodeInfos[nodeIdString]; nodeInfo.position = QVector3D(x, y, z); nodeInfo.radius = radius; nodeInfo.boneMark = boneMark; + nodeInfo.hasCutFaceSettings = hasCutFaceSettings; + nodeInfo.cutRotation = cutRotation; + nodeInfo.cutFace = cutFace; } std::set> edges; @@ -428,7 +468,16 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt for (const auto &nodeIt: nodeInfos) { const auto &nodeIdString = nodeIt.first; const auto &nodeInfo = nodeIt.second; - size_t nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate); + size_t nodeIndex = 0; + if (nodeInfo.hasCutFaceSettings) { + std::vector nodeCutTemplate; + cutFaceStringToCutTemplate(nodeInfo.cutFace, nodeCutTemplate); + if (chamfered) + nodemesh::chamferFace2D(&nodeCutTemplate); + nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, nodeCutTemplate, nodeInfo.cutRotation); + } else { + nodeIndex = modifier->addNode(nodeInfo.position, nodeInfo.radius, cutTemplate, cutRotation); + } nodeIdStringToIndexMap[nodeIdString] = nodeIndex; nodeIndexToIdStringMap[nodeIndex] = nodeIdString; @@ -482,7 +531,6 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt nodemesh::Builder *builder = new nodemesh::Builder; builder->setDeformThickness(deformThickness); builder->setDeformWidth(deformWidth); - builder->setCutRotation(cutRotation); if (PartBase::YZ == base) { builder->enableBaseNormalOnX(false); } else if (PartBase::Average == base) { @@ -495,7 +543,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt std::vector builderNodeIndices; for (const auto &node: modifier->nodes()) { - auto nodeIndex = builder->addNode(node.position, node.radius, node.cutTemplate); + auto nodeIndex = builder->addNode(node.position, node.radius, node.cutTemplate, node.cutRotation); builder->setNodeOriginInfo(nodeIndex, node.nearOriginNodeIndex, node.farOriginNodeIndex); builderNodeIndices.push_back(nodeIndex); } @@ -503,19 +551,19 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt builder->addEdge(edge.firstNodeIndex, edge.secondNodeIndex); bool buildSucceed = builder->build(); - for (size_t i = 0; i < modifier->nodes().size(); ++i) { - const auto &node = modifier->nodes()[i]; - if (!node.isOriginal) - continue; - const QString &nodeIdString = nodeIndexToIdStringMap[node.originNodeIndex]; - const nodemesh::Builder::CutFaceTransform *cutFaceTransform = builder->nodeAdjustableCutFaceTransform(builderNodeIndices[i]); - if (nullptr != cutFaceTransform && - PartTarget::Model == target) { - QUuid nodeId = QUuid(nodeIdString); - m_cutFaceTransforms->insert({nodeId, *cutFaceTransform}); - m_nodesCutFaces->insert({nodeId, cutTemplateMapByName}); - } - } + //for (size_t i = 0; i < modifier->nodes().size(); ++i) { + // const auto &node = modifier->nodes()[i]; + // if (!node.isOriginal) + // continue; + // const QString &nodeIdString = nodeIndexToIdStringMap[node.originNodeIndex]; + // const nodemesh::Builder::CutFaceTransform *cutFaceTransform = builder->nodeAdjustableCutFaceTransform(builderNodeIndices[i]); + // if (nullptr != cutFaceTransform && + // PartTarget::Model == target) { + // QUuid nodeId = QUuid(nodeIdString); + // m_cutFaceTransforms->insert({nodeId, *cutFaceTransform}); + // m_nodesCutFaces->insert({nodeId, cutTemplateMapByName}); + // } + //} partCache.vertices = builder->generatedVertices(); partCache.faces = builder->generatedFaces(); @@ -531,7 +579,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt nodemesh::Combiner::Mesh *mesh = nullptr; if (buildSucceed) { - mesh = new nodemesh::Combiner::Mesh(partCache.vertices, partCache.faces); + mesh = new nodemesh::Combiner::Mesh(partCache.vertices, partCache.faces, false); if (!mesh->isNull()) { if (xMirrored) { std::vector xMirroredVertices; @@ -1008,8 +1056,8 @@ void MeshGenerator::generate() countTimeConsumed.start(); m_outcome = new Outcome; - m_cutFaceTransforms = new std::map; - m_nodesCutFaces = new std::map>; + //m_cutFaceTransforms = new std::map; + //m_nodesCutFaces = new std::map>; bool needDeleteCacheContext = false; if (nullptr == m_cacheContext) { diff --git a/src/meshgenerator.h b/src/meshgenerator.h index 89528f69..ce4c296e 100644 --- a/src/meshgenerator.h +++ b/src/meshgenerator.h @@ -116,6 +116,7 @@ private: nodemesh::Combiner::Mesh *combineMultipleMeshes(const std::vector> &multipleMeshes, bool recombine=true); QString componentColorName(const std::map *component); void collectUncombinedComponent(const QString &componentIdString); + void cutFaceStringToCutTemplate(const QString &cutFaceString, std::vector &cutTemplate); }; #endif diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 4d9dd56b..edebe348 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -22,7 +22,10 @@ public: y(0), z(0), radius(0), - boneMark(BoneMark::None) + boneMark(BoneMark::None), + cutRotation(0.0), + cutFace(CutFace::Quad), + hasCutFaceSettings(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -34,6 +37,38 @@ public: toRadius = 1; radius = toRadius; } + void setCutRotation(float toRotation) + { + if (toRotation < -1) + toRotation = -1; + else if (toRotation > 1) + toRotation = 1; + cutRotation = toRotation; + hasCutFaceSettings = true; + } + void setCutFace(CutFace face) + { + cutFace = face; + cutFaceLinkedId = QUuid(); + hasCutFaceSettings = true; + } + void setCutFaceLinkedId(const QUuid &linkedId) + { + if (linkedId.isNull()) { + clearCutFaceSettings(); + return; + } + cutFace = CutFace::UserDefined; + cutFaceLinkedId = linkedId; + hasCutFaceSettings = true; + } + void clearCutFaceSettings() + { + cutFace = CutFace::Quad; + cutFaceLinkedId = QUuid(); + cutRotation = 0; + hasCutFaceSettings = false; + } QUuid id; QUuid partId; QString name; @@ -42,6 +77,10 @@ public: float z; float radius; BoneMark boneMark; + float cutRotation; + CutFace cutFace; + QUuid cutFaceLinkedId; + bool hasCutFaceSettings; std::vector edgeIds; }; diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index 644ec4af..4fc3ffde 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -219,6 +219,20 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos) contextMenu.addAction(&switchChainSideAction); } + QAction setCutFaceAction(tr("Cut Face..."), this); + if (!m_nodePositionModifyOnly && hasSelection()) { + connect(&setCutFaceAction, &QAction::triggered, this, [&]() { + showSelectedCutFaceSettingPopup(mapFromGlobal(QCursor::pos())); + }); + contextMenu.addAction(&setCutFaceAction); + } + + QAction clearCutFaceAction(tr("Clear Cut Face"), this); + if (!m_nodePositionModifyOnly && hasCutFaceAdjustedNodesSelection()) { + connect(&clearCutFaceAction, &QAction::triggered, this, &SkeletonGraphicsWidget::clearSelectedCutFace); + contextMenu.addAction(&clearCutFaceAction); + } + QAction alignToLocalCenterAction(tr("Local Center"), this); QAction alignToLocalVerticalCenterAction(tr("Local Vertical Center"), this); QAction alignToLocalHorizontalCenterAction(tr("Local Horizontal Center"), this); @@ -353,6 +367,23 @@ bool SkeletonGraphicsWidget::hasTwoDisconnectedNodesSelection() return true; } +bool SkeletonGraphicsWidget::hasCutFaceAdjustedNodesSelection() +{ + for (const auto &it: m_rangeSelectionSet) { + if (it->data(0) == "node") { + const auto &nodeId = ((SkeletonGraphicsNodeItem *)it)->id(); + const SkeletonNode *node = m_document->findNode(nodeId); + if (nullptr == node) { + qDebug() << "Find node failed:" << nodeId; + continue; + } + if (node->hasCutFaceSettings) + return true; + } + } + return false; +} + void SkeletonGraphicsWidget::breakSelected() { std::set edgeIds; @@ -2613,6 +2644,40 @@ void SkeletonGraphicsWidget::setSelectedNodesBoneMark(BoneMark boneMark) } } +void SkeletonGraphicsWidget::showSelectedCutFaceSettingPopup(const QPoint &pos) +{ + std::set nodeIdSet; + std::set edgeIdSet; + readSkeletonNodeAndEdgeIdSetFromRangeSelection(&nodeIdSet, &edgeIdSet); + emit showCutFaceSettingPopup(mapToGlobal(pos), nodeIdSet); +} + +void SkeletonGraphicsWidget::clearSelectedCutFace() +{ + std::set nodeIdSet; + for (const auto &it: m_rangeSelectionSet) { + if (it->data(0) == "node") { + const auto &nodeId = ((SkeletonGraphicsNodeItem *)it)->id(); + const SkeletonNode *node = m_document->findNode(nodeId); + if (nullptr == node) { + qDebug() << "Find node failed:" << nodeId; + continue; + } + if (node->hasCutFaceSettings) { + nodeIdSet.insert(nodeId); + } + } + } + if (nodeIdSet.empty()) + return; + emit batchChangeBegin(); + for (const auto &id: nodeIdSet) { + emit clearNodeCutFaceSettings(id); + } + emit batchChangeEnd(); + emit groupOperationAdded(); +} + void SkeletonGraphicsWidget::setNodePositionModifyOnly(bool nodePositionModifyOnly) { m_nodePositionModifyOnly = nodePositionModifyOnly; diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index fa525ea1..f256c73a 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -406,6 +406,8 @@ signals: void setZlockState(bool locked); void setNodeOrigin(QUuid nodeId, float x, float y, float z); void setNodeBoneMark(QUuid nodeId, BoneMark mark); + void clearNodeCutFaceSettings(QUuid nodeId); + void showCutFaceSettingPopup(const QPoint &globalPos, std::set nodeIds); void zoomRenderedModelBy(float delta); void switchNodeXZ(QUuid nodeId); void switchChainSide(std::set nodeIds); @@ -437,6 +439,7 @@ public: bool hasEdgeSelection(); bool hasNodeSelection(); bool hasTwoDisconnectedNodesSelection(); + bool hasCutFaceAdjustedNodesSelection(); void setModelWidget(ModelWidget *modelWidget); void setNodePositionModifyOnly(bool nodePositionModifyOnly); void setMainProfileOnly(bool mainProfileOnly); @@ -505,6 +508,8 @@ public slots: void timeToRemoveDeferredNodesAndEdges(); void switchSelectedXZ(); void switchSelectedChainSide(); + void showSelectedCutFaceSettingPopup(const QPoint &pos); + void clearSelectedCutFace(); void shortcutDelete(); void shortcutAddMode(); void shortcutUndo(); diff --git a/thirdparty/nodemesh/nodemesh/builder.cpp b/thirdparty/nodemesh/nodemesh/builder.cpp index 9e7882b4..2cb53375 100644 --- a/thirdparty/nodemesh/nodemesh/builder.cpp +++ b/thirdparty/nodemesh/nodemesh/builder.cpp @@ -16,13 +16,14 @@ namespace nodemesh { -size_t Builder::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate) +size_t Builder::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) { size_t nodeIndex = m_nodes.size(); Node node; node.position = position; node.radius = radius; node.cutTemplate = cutTemplate; + node.cutRotation = cutRotation; m_nodes.push_back(node); m_sortedNodeIndices.push_back(nodeIndex); //qDebug() << "addNode" << position << radius; @@ -452,7 +453,7 @@ bool Builder::generateCutsForNode(size_t nodeIndex) if (1 == neighborsCount) { QVector3D cutNormal = node.cutNormal; std::vector cut; - makeCut(node.position, node.radius, node.cutTemplate, node.baseNormal, cutNormal, node.traverseDirection, cut, &node.cutFaceTransform); + makeCut(node.position, node.radius, node.cutTemplate, node.cutRotation, node.baseNormal, cutNormal, node.traverseDirection, cut, &node.cutFaceTransform); node.hasAdjustableCutFace = true; std::vector vertices; insertCutVertices(cut, vertices, nodeIndex, cutNormal); @@ -477,7 +478,7 @@ bool Builder::generateCutsForNode(size_t nodeIndex) } } std::vector cut; - makeCut(node.position, node.radius, node.cutTemplate, node.baseNormal, cutNormal, node.traverseDirection, cut, &node.cutFaceTransform); + makeCut(node.position, node.radius, node.cutTemplate, node.cutRotation, node.baseNormal, cutNormal, node.traverseDirection, cut, &node.cutFaceTransform); node.hasAdjustableCutFace = true; std::vector vertices; insertCutVertices(cut, vertices, nodeIndex, cutNormal); @@ -549,7 +550,7 @@ bool Builder::tryWrapMultipleBranchesForNode(size_t nodeIndex, std::vector vertices; insertCutVertices(cut, vertices, nodeIndex, cutNormal); cutsForEdges.push_back({vertices, -cutNormal}); @@ -715,6 +716,7 @@ QVector3D Builder::revisedBaseNormalAcordingToCutNormal(const QVector3D &baseNor void Builder::makeCut(const QVector3D &position, float radius, const std::vector &cutTemplate, + float cutRotation, QVector3D &baseNormal, QVector3D &cutNormal, const QVector3D &traverseDirection, @@ -723,8 +725,8 @@ void Builder::makeCut(const QVector3D &position, { auto finalCutTemplate = cutTemplate; float degree = 0; - if (!qFuzzyIsNull(m_cutRotation)) { - degree = m_cutRotation * 180; + if (!qFuzzyIsNull(cutRotation)) { + degree = cutRotation * 180; } if (QVector3D::dotProduct(cutNormal, traverseDirection) <= 0) { cutNormal = -cutNormal; @@ -834,11 +836,6 @@ void Builder::setDeformWidth(float width) m_deformWidth = width; } -void Builder::setCutRotation(float cutRotation) -{ - m_cutRotation = cutRotation; -} - QVector3D Builder::calculateDeformPosition(const QVector3D &vertexPosition, const QVector3D &ray, const QVector3D &deformNormal, float deformFactor) { QVector3D revisedNormal = QVector3D::dotProduct(ray, deformNormal) < 0.0 ? -deformNormal : deformNormal; diff --git a/thirdparty/nodemesh/nodemesh/builder.h b/thirdparty/nodemesh/nodemesh/builder.h index a31216a9..abee4430 100644 --- a/thirdparty/nodemesh/nodemesh/builder.h +++ b/thirdparty/nodemesh/nodemesh/builder.h @@ -23,12 +23,11 @@ public: bool reverse = false; }; - size_t addNode(const QVector3D &position, float radius, const std::vector &cutTemplate); + size_t addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation); size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex); void setNodeOriginInfo(size_t nodeIndex, int nearOriginNodeIndex, int farOriginNodeIndex); void setDeformThickness(float thickness); void setDeformWidth(float width); - void setCutRotation(float cutRotation); void enableBaseNormalOnX(bool enabled); void enableBaseNormalOnY(bool enabled); void enableBaseNormalOnZ(bool enabled); @@ -50,6 +49,7 @@ private: QVector3D position; std::vector edges; std::vector cutTemplate; + float cutRotation; std::vector raysToNeibors; QVector3D cutNormal; CutFaceTransform cutFaceTransform; @@ -137,6 +137,7 @@ private: void makeCut(const QVector3D &position, float radius, const std::vector &cutTemplate, + float cutRotation, QVector3D &baseNormal, QVector3D &cutNormal, const QVector3D &traverseDirection, diff --git a/thirdparty/nodemesh/nodemesh/modifier.cpp b/thirdparty/nodemesh/nodemesh/modifier.cpp index 5d276b6b..c3edfc51 100644 --- a/thirdparty/nodemesh/nodemesh/modifier.cpp +++ b/thirdparty/nodemesh/nodemesh/modifier.cpp @@ -6,7 +6,7 @@ namespace nodemesh { -size_t Modifier::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate) +size_t Modifier::addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation) { size_t nodeIndex = m_nodes.size(); @@ -15,6 +15,7 @@ size_t Modifier::addNode(const QVector3D &position, float radius, const std::vec node.position = position; node.radius = radius; node.cutTemplate = cutTemplate; + node.cutRotation = cutRotation; node.originNodeIndex = nodeIndex; m_nodes.push_back(node); @@ -38,11 +39,18 @@ void Modifier::createIntermediateNode(const Node &firstNode, const Node &secondN float firstFactor = 1.0 - factor; resultNode->position = firstNode.position * firstFactor + secondNode.position * factor; resultNode->radius = firstNode.radius * firstFactor + secondNode.radius * factor; - resultNode->cutTemplate = firstNode.cutTemplate; - for (size_t i = 0; i < secondNode.cutTemplate.size(); ++i) { - if (i >= resultNode->cutTemplate.size()) - break; - resultNode->cutTemplate[i] = resultNode->cutTemplate[i] * firstFactor + secondNode.cutTemplate[i] * factor; + if (factor <= 0.5) { + resultNode->originNodeIndex = firstNode.originNodeIndex; + resultNode->nearOriginNodeIndex = firstNode.originNodeIndex; + resultNode->farOriginNodeIndex = secondNode.originNodeIndex; + resultNode->cutRotation = firstNode.cutRotation; + resultNode->cutTemplate = firstNode.cutTemplate; + } else { + resultNode->originNodeIndex = secondNode.originNodeIndex; + resultNode->nearOriginNodeIndex = secondNode.originNodeIndex; + resultNode->farOriginNodeIndex = firstNode.originNodeIndex; + resultNode->cutRotation = secondNode.cutRotation; + resultNode->cutTemplate = secondNode.cutTemplate; } } @@ -81,6 +89,7 @@ void Modifier::roundEnd() endNode.radius = currentNode.radius * 0.5; endNode.position = currentNode.position + (currentNode.position - neighborNode.position).normalized() * endNode.radius; endNode.cutTemplate = currentNode.cutTemplate; + endNode.cutRotation = currentNode.cutRotation; endNode.originNodeIndex = currentNode.originNodeIndex; size_t endNodeIndex = m_nodes.size(); m_nodes.push_back(endNode); @@ -117,15 +126,6 @@ void Modifier::finalize() const Node &firstNode = m_nodes[edge.firstNodeIndex]; const Node &secondNode = m_nodes[edge.secondNodeIndex]; createIntermediateNode(firstNode, secondNode, factor, &intermediateNode); - if (factor <= 0.5) { - intermediateNode.originNodeIndex = firstNode.originNodeIndex; - intermediateNode.nearOriginNodeIndex = firstNode.originNodeIndex; - intermediateNode.farOriginNodeIndex = secondNode.originNodeIndex; - } else { - intermediateNode.originNodeIndex = secondNode.originNodeIndex; - intermediateNode.nearOriginNodeIndex = secondNode.originNodeIndex; - intermediateNode.farOriginNodeIndex = firstNode.originNodeIndex; - } size_t intermedidateNodeIndex = m_nodes.size(); nodeIndices.push_back(intermedidateNodeIndex); m_nodes.push_back(intermediateNode); diff --git a/thirdparty/nodemesh/nodemesh/modifier.h b/thirdparty/nodemesh/nodemesh/modifier.h index fa589582..0f1f4d2a 100644 --- a/thirdparty/nodemesh/nodemesh/modifier.h +++ b/thirdparty/nodemesh/nodemesh/modifier.h @@ -15,6 +15,7 @@ public: QVector3D position; float radius = 0.0; std::vector cutTemplate; + float cutRotation = 0.0; int nearOriginNodeIndex = -1; int farOriginNodeIndex = -1; int originNodeIndex = 0; @@ -26,7 +27,7 @@ public: size_t secondNodeIndex; }; - size_t addNode(const QVector3D &position, float radius, const std::vector &cutTemplate); + size_t addNode(const QVector3D &position, float radius, const std::vector &cutTemplate, float cutRotation); size_t addEdge(size_t firstNodeIndex, size_t secondNodeIndex); void subdivide(); void roundEnd();