From 22910028e98b5331c8ac24a8aab8619e514ab3c4 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sat, 16 Jun 2018 14:41:52 +0800 Subject: [PATCH] Add parts subtract operation. This is useful for making hole surface. For example, without subtraction, it's difficult to make a bucket like mesh, now, you just need to copy and paste the nodes, invert it from the Parts List Panel Context Menu, then down scale and move up a little bit. --- docs/interface/parts_panel.rst | 24 +++++++- src/meshgenerator.cpp | 13 +++- src/meshutil.cpp | 23 ++++++- src/meshutil.h | 3 +- src/skeletondocument.cpp | 106 +++++++++++++++++++++++++++++++++ src/skeletondocument.h | 11 +++- src/skeletonpartlistwidget.cpp | 55 ++++++++++++++++- src/skeletonpartlistwidget.h | 5 ++ 8 files changed, 227 insertions(+), 13 deletions(-) diff --git a/docs/interface/parts_panel.rst b/docs/interface/parts_panel.rst index a8329b93..fb98308e 100644 --- a/docs/interface/parts_panel.rst +++ b/docs/interface/parts_panel.rst @@ -3,7 +3,7 @@ Parts List Panel .. image:: https://raw.githubusercontent.com/huxingyi/dust3d/master/docs/interface/parts-list-panel.png -In Dust3D, model consists of parts, part consists of nodes. User manipulats nodes's position and radius, toggle part's settings, then the mesh autogenerated by Dust3D. +In Dust3D, model consists of parts, part consists of nodes. User manipulates nodes's position and radius, toggle part's settings, then the mesh autogenerated by Dust3D. Top Mini Buttons ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -39,7 +39,7 @@ Toggle the basic unit type of mesh generation. Box means quad face, subdivisione * Thickness Modifier This is used when you want make fish body like mesh, or a thin or thick plane. -The are two parameters for this modifier, one controls the thickness along the base plane normal, the other controls the thickness along the perpendicular of base plane normal. +The are two parameters for this modifier, one controls the thickness along the base plane normal, the other controls the thickness along the perpendicular of base plane normal. * (M)irror Modifier @@ -48,3 +48,23 @@ If there are two parts have the same shape but one sit left, the other sit right * End Ro(U)ndable Modifier Toggle the begin/end roundable (Flat / Rounded). If you want the cut effect, choose not rounded, otherwise, the edge end will be rounded (Automatically added one more small face). + +Context Menu +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Right click on Parts List Panel to trigger Context Menu. + +* Hide Part / Show Part / Hide Other Parts / Show All Parts / Hide All Parts + +Check mini button **Visible/(H)idden**. + +* Lock All Parts / Unlock All Parts + +Check mini button **_(L)ock/Unlock**. + +* Invert Part / Cancel Inverse + +Inverting a part means subtract this part from other parts which sit before this part. + +* Move Up / Move Down / Move To Top / Move To Bottom + +The process of mesh generation is mainly combining all the parts in listed order, so move up and down may affect the generated result. The order is especially important for inverse part been placed, if the inverse part sit in the first place of the list, the inverse operation would never happen, because nothing to invert before it. diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index a34119cf..636f991f 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -348,6 +348,7 @@ void MeshGenerator::process() std::vector meshIds; std::vector subdivMeshIds; + std::set inverseIds; for (const auto &partIdIt: m_snapshot->partIdList) { const auto &part = m_snapshot->parts.find(partIdIt); if (part == m_snapshot->parts.end()) @@ -378,15 +379,21 @@ void MeshGenerator::process() m_partPreviewMap[partIdIt] = image; } meshIds.push_back(meshId); - if (xMirroredMeshId) + bool inverse = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "inverse")); + if (inverse) + inverseIds.insert(meshId); + if (xMirroredMeshId) { meshIds.push_back(xMirroredMeshId); + if (inverse) + inverseIds.insert(xMirroredMeshId); + } } if (!subdivMeshIds.empty()) { int mergedMeshId = 0; if (subdivMeshIds.size() > 1) { int errorCount = 0; - mergedMeshId = unionMeshs(meshliteContext, subdivMeshIds, &errorCount); + mergedMeshId = unionMeshs(meshliteContext, subdivMeshIds, inverseIds, &errorCount); if (errorCount) broken = true; } else { @@ -416,7 +423,7 @@ void MeshGenerator::process() if (disableUnion) mergedMeshId = mergeMeshs(meshliteContext, meshIds); else - mergedMeshId = unionMeshs(meshliteContext, meshIds, &errorCount); + mergedMeshId = unionMeshs(meshliteContext, meshIds, inverseIds, &errorCount); if (errorCount) broken = true; else if (mergedMeshId > 0) diff --git a/src/meshutil.cpp b/src/meshutil.cpp index efc99789..f55f70f0 100644 --- a/src/meshutil.cpp +++ b/src/meshutil.cpp @@ -140,7 +140,21 @@ ExactMesh *unionCgalMeshs(ExactMesh *first, ExactMesh *second) delete mesh; return nullptr; } - //CGAL::Polygon_mesh_processing::isotropic_remeshing(mesh->faces(), 0.4, *mesh); + } catch (...) { + delete mesh; + return nullptr; + } + return mesh; +} + +ExactMesh *diffCgalMeshs(ExactMesh *first, ExactMesh *second) +{ + ExactMesh *mesh = new ExactMesh; + try { + if (!PMP::corefine_and_compute_difference(*first, *second, *mesh)) { + delete mesh; + return nullptr; + } } catch (...) { delete mesh; return nullptr; @@ -150,7 +164,7 @@ ExactMesh *unionCgalMeshs(ExactMesh *first, ExactMesh *second) #endif -int unionMeshs(void *meshliteContext, const std::vector &meshIds, int *errorCount) +int unionMeshs(void *meshliteContext, const std::vector &meshIds, const std::set &inverseIds, int *errorCount) { #if USE_CGAL == 1 CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); @@ -185,7 +199,10 @@ int unionMeshs(void *meshliteContext, const std::vector &meshIds, int *erro } ExactMesh *unionedExternalMesh = NULL; try { - unionedExternalMesh = unionCgalMeshs(mergedExternalMesh, externalMeshs[i]); + if (inverseIds.find(meshIds[i]) == inverseIds.end()) + unionedExternalMesh = unionCgalMeshs(mergedExternalMesh, externalMeshs[i]); + else + unionedExternalMesh = diffCgalMeshs(mergedExternalMesh, externalMeshs[i]); } catch (...) { qDebug() << "unionCgalMeshs throw exception"; if (errorCount) diff --git a/src/meshutil.h b/src/meshutil.h index bcb4145d..d0a85d07 100644 --- a/src/meshutil.h +++ b/src/meshutil.h @@ -1,9 +1,10 @@ #ifndef MESH_UTIL_H #define MESH_UTIL_H #include +#include int mergeMeshs(void *meshliteContext, const std::vector &meshIds); -int unionMeshs(void *meshliteContext, const std::vector &meshIds, int *errorCount=0); +int unionMeshs(void *meshliteContext, const std::vector &meshIds, const std::set &inverseIds, int *errorCount=0); int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0); int fixMeshHoles(void *meshliteContext, int meshId); diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index d3157a46..dd976d24 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -640,6 +640,7 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setsecond); @@ -1210,6 +1212,20 @@ void SkeletonDocument::setPartVisibleState(QUuid partId, bool visible) emit partVisibleStateChanged(partId); } +void SkeletonDocument::setPartInverseState(QUuid partId, bool inverse) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (part->second.inverse == inverse) + return; + part->second.inverse = inverse; + emit partInverseStateChanged(partId); + emit skeletonChanged(); +} + void SkeletonDocument::setPartSubdivState(QUuid partId, bool subdived) { auto part = partMap.find(partId); @@ -1238,6 +1254,96 @@ void SkeletonDocument::setPartDisableState(QUuid partId, bool disabled) emit skeletonChanged(); } +void SkeletonDocument::movePartUp(QUuid partId) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + + auto it = std::find(partIds.begin(), partIds.end(), partId); + if (it == partIds.end()) { + qDebug() << "Part not found in list:" << partId; + return; + } + + auto index = std::distance(partIds.begin(), it); + if (index == 0) + return; + std::swap(partIds[index - 1], partIds[index]); + emit partListChanged(); + emit skeletonChanged(); +} + +void SkeletonDocument::movePartDown(QUuid partId) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + + auto it = std::find(partIds.begin(), partIds.end(), partId); + if (it == partIds.end()) { + qDebug() << "Part not found in list:" << partId; + return; + } + + auto index = std::distance(partIds.begin(), it); + if (index == (int)partIds.size() - 1) + return; + std::swap(partIds[index], partIds[index + 1]); + emit partListChanged(); + emit skeletonChanged(); +} + +void SkeletonDocument::movePartToTop(QUuid partId) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + + auto it = std::find(partIds.begin(), partIds.end(), partId); + if (it == partIds.end()) { + qDebug() << "Part not found in list:" << partId; + return; + } + + auto index = std::distance(partIds.begin(), it); + if (index == 0) + return; + for (int i = index; i >= 1; i--) + std::swap(partIds[i - 1], partIds[i]); + emit partListChanged(); + emit skeletonChanged(); +} + +void SkeletonDocument::movePartToBottom(QUuid partId) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + + auto it = std::find(partIds.begin(), partIds.end(), partId); + if (it == partIds.end()) { + qDebug() << "Part not found in list:" << partId; + return; + } + + auto index = std::distance(partIds.begin(), it); + if (index == (int)partIds.size() - 1) + return; + for (int i = index; i <= (int)partIds.size() - 2; i++) + std::swap(partIds[i], partIds[i + 1]); + emit partListChanged(); + emit skeletonChanged(); +} + void SkeletonDocument::settleOrigin() { if (originSettled()) diff --git a/src/skeletondocument.h b/src/skeletondocument.h index a64e2dd8..4dbee2b3 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -86,6 +86,7 @@ public: bool rounded; QColor color; bool hasColor; + bool inverse; QImage preview; std::vector nodeIds; SkeletonPart(const QUuid &withId=QUuid()) : @@ -99,7 +100,8 @@ public: deformWidth(1.0), rounded(false), color(Theme::white), - hasColor(false) + hasColor(false), + inverse(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -148,6 +150,7 @@ public: rounded = other.rounded; color = other.color; hasColor = other.hasColor; + inverse = other.inverse; } }; @@ -217,6 +220,7 @@ signals: void partDeformWidthChanged(QUuid partId); void partRoundStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); + void partInverseStateChanged(QUuid partId); void cleanup(); void originChanged(); void xlockStateChanged(); @@ -314,6 +318,11 @@ public slots: void setPartDeformWidth(QUuid partId, float width); void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); + void setPartInverseState(QUuid partId, bool inverse); + void movePartUp(QUuid partId); + void movePartDown(QUuid partId); + void movePartToTop(QUuid partId); + void movePartToBottom(QUuid partId); void saveSnapshot(); void undo(); void redo(); diff --git a/src/skeletonpartlistwidget.cpp b/src/skeletonpartlistwidget.cpp index 2d635882..30b81eba 100644 --- a/src/skeletonpartlistwidget.cpp +++ b/src/skeletonpartlistwidget.cpp @@ -123,6 +123,11 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p connect(this, &SkeletonPartWidget::setPartDeformWidth, m_document, &SkeletonDocument::setPartDeformWidth); connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState); connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState); + connect(this, &SkeletonPartWidget::setPartInverseState, m_document, &SkeletonDocument::setPartInverseState); + connect(this, &SkeletonPartWidget::movePartUp, m_document, &SkeletonDocument::movePartUp); + connect(this, &SkeletonPartWidget::movePartDown, m_document, &SkeletonDocument::movePartDown); + connect(this, &SkeletonPartWidget::movePartToTop, m_document, &SkeletonDocument::movePartToTop); + connect(this, &SkeletonPartWidget::movePartToBottom, m_document, &SkeletonDocument::movePartToBottom); connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart); connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur); connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur); @@ -206,6 +211,9 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p }); setContextMenuPolicy(Qt::CustomContextMenu); + connect(this, &SkeletonPartWidget::customContextMenuRequested, [=] { + emit checkPart(m_partId); + }); connect(this, &SkeletonPartWidget::customContextMenuRequested, this, &SkeletonPartWidget::showContextMenu); updateAllButtons(); @@ -227,8 +235,6 @@ void SkeletonPartWidget::updateAllButtons() void SkeletonPartWidget::showContextMenu(const QPoint &pos) { - emit checkPart(m_partId); - QMenu contextMenu(this); const SkeletonPart *part = m_document->findPart(m_partId); @@ -274,6 +280,8 @@ void SkeletonPartWidget::showContextMenu(const QPoint &pos) }); contextMenu.addAction(&hideAllPartsAction); + contextMenu.addSeparator(); + QAction lockAllPartsAction(tr("Lock All Parts"), this); connect(&lockAllPartsAction, &QAction::triggered, [=]() { for (const auto &it: m_document->partIds) { @@ -289,11 +297,52 @@ void SkeletonPartWidget::showContextMenu(const QPoint &pos) } }); contextMenu.addAction(&unlockAllPartsAction); + + contextMenu.addSeparator(); + + QAction invertPartAction(tr("Invert Part"), this); + if (part && !part->inverse) { + connect(&invertPartAction, &QAction::triggered, [=]() { + emit setPartInverseState(m_partId, true); + }); + contextMenu.addAction(&invertPartAction); + } + + QAction cancelInverseAction(tr("Cancel Inverse"), this); + if (part && part->inverse) { + connect(&cancelInverseAction, &QAction::triggered, [=]() { + emit setPartInverseState(m_partId, false); + }); + contextMenu.addAction(&cancelInverseAction); + } + + QAction moveUpAction(tr("Move Up"), this); + connect(&moveUpAction, &QAction::triggered, [=]() { + emit movePartUp(m_partId); + }); + contextMenu.addAction(&moveUpAction); + + QAction moveDownAction(tr("Move Down"), this); + connect(&moveDownAction, &QAction::triggered, [=]() { + emit movePartDown(m_partId); + }); + contextMenu.addAction(&moveDownAction); + + QAction moveToTopAction(tr("Move To Top"), this); + connect(&moveToTopAction, &QAction::triggered, [=]() { + emit movePartToTop(m_partId); + }); + contextMenu.addAction(&moveToTopAction); + + QAction moveToBottomAction(tr("Move To Bottom"), this); + connect(&moveToBottomAction, &QAction::triggered, [=]() { + emit movePartToBottom(m_partId); + }); + contextMenu.addAction(&moveToBottomAction); contextMenu.exec(mapToGlobal(pos)); } - void SkeletonPartWidget::updateCheckedState(bool checked) { if (checked) diff --git a/src/skeletonpartlistwidget.h b/src/skeletonpartlistwidget.h index 3c4cf8eb..f9d66084 100644 --- a/src/skeletonpartlistwidget.h +++ b/src/skeletonpartlistwidget.h @@ -21,6 +21,11 @@ signals: void setPartDeformWidth(QUuid partId, float width); void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); + void setPartInverseState(QUuid partId, bool inverse); + void movePartUp(QUuid partId); + void movePartDown(QUuid partId); + void movePartToTop(QUuid partId); + void movePartToBottom(QUuid partId); void checkPart(QUuid partId); void groupOperationAdded(); void enableBackgroundBlur();