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.
master
Jeremy Hu 2018-06-16 14:41:52 +08:00
parent 35635534c2
commit 22910028e9
8 changed files with 227 additions and 13 deletions

View File

@ -3,7 +3,7 @@ Parts List Panel
.. image:: https://raw.githubusercontent.com/huxingyi/dust3d/master/docs/interface/parts-list-panel.png .. 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 Top Mini Buttons
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -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 * 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). 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.

View File

@ -348,6 +348,7 @@ void MeshGenerator::process()
std::vector<int> meshIds; std::vector<int> meshIds;
std::vector<int> subdivMeshIds; std::vector<int> subdivMeshIds;
std::set<int> inverseIds;
for (const auto &partIdIt: m_snapshot->partIdList) { for (const auto &partIdIt: m_snapshot->partIdList) {
const auto &part = m_snapshot->parts.find(partIdIt); const auto &part = m_snapshot->parts.find(partIdIt);
if (part == m_snapshot->parts.end()) if (part == m_snapshot->parts.end())
@ -378,15 +379,21 @@ void MeshGenerator::process()
m_partPreviewMap[partIdIt] = image; m_partPreviewMap[partIdIt] = image;
} }
meshIds.push_back(meshId); meshIds.push_back(meshId);
if (xMirroredMeshId) bool inverse = isTrueValueString(valueOfKeyInMapOrEmpty(part->second, "inverse"));
if (inverse)
inverseIds.insert(meshId);
if (xMirroredMeshId) {
meshIds.push_back(xMirroredMeshId); meshIds.push_back(xMirroredMeshId);
if (inverse)
inverseIds.insert(xMirroredMeshId);
}
} }
if (!subdivMeshIds.empty()) { if (!subdivMeshIds.empty()) {
int mergedMeshId = 0; int mergedMeshId = 0;
if (subdivMeshIds.size() > 1) { if (subdivMeshIds.size() > 1) {
int errorCount = 0; int errorCount = 0;
mergedMeshId = unionMeshs(meshliteContext, subdivMeshIds, &errorCount); mergedMeshId = unionMeshs(meshliteContext, subdivMeshIds, inverseIds, &errorCount);
if (errorCount) if (errorCount)
broken = true; broken = true;
} else { } else {
@ -416,7 +423,7 @@ void MeshGenerator::process()
if (disableUnion) if (disableUnion)
mergedMeshId = mergeMeshs(meshliteContext, meshIds); mergedMeshId = mergeMeshs(meshliteContext, meshIds);
else else
mergedMeshId = unionMeshs(meshliteContext, meshIds, &errorCount); mergedMeshId = unionMeshs(meshliteContext, meshIds, inverseIds, &errorCount);
if (errorCount) if (errorCount)
broken = true; broken = true;
else if (mergedMeshId > 0) else if (mergedMeshId > 0)

View File

@ -140,7 +140,21 @@ ExactMesh *unionCgalMeshs(ExactMesh *first, ExactMesh *second)
delete mesh; delete mesh;
return nullptr; 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 (...) { } catch (...) {
delete mesh; delete mesh;
return nullptr; return nullptr;
@ -150,7 +164,7 @@ ExactMesh *unionCgalMeshs(ExactMesh *first, ExactMesh *second)
#endif #endif
int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, int *errorCount) int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &inverseIds, int *errorCount)
{ {
#if USE_CGAL == 1 #if USE_CGAL == 1
CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION); CGAL::set_error_behaviour(CGAL::THROW_EXCEPTION);
@ -185,7 +199,10 @@ int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, int *erro
} }
ExactMesh *unionedExternalMesh = NULL; ExactMesh *unionedExternalMesh = NULL;
try { try {
if (inverseIds.find(meshIds[i]) == inverseIds.end())
unionedExternalMesh = unionCgalMeshs(mergedExternalMesh, externalMeshs[i]); unionedExternalMesh = unionCgalMeshs(mergedExternalMesh, externalMeshs[i]);
else
unionedExternalMesh = diffCgalMeshs(mergedExternalMesh, externalMeshs[i]);
} catch (...) { } catch (...) {
qDebug() << "unionCgalMeshs throw exception"; qDebug() << "unionCgalMeshs throw exception";
if (errorCount) if (errorCount)

View File

@ -1,9 +1,10 @@
#ifndef MESH_UTIL_H #ifndef MESH_UTIL_H
#define MESH_UTIL_H #define MESH_UTIL_H
#include <vector> #include <vector>
#include <set>
int mergeMeshs(void *meshliteContext, const std::vector<int> &meshIds); int mergeMeshs(void *meshliteContext, const std::vector<int> &meshIds);
int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, int *errorCount=0); int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &inverseIds, int *errorCount=0);
int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0); int subdivMesh(void *meshliteContext, int meshId, int *errorCount=0);
int fixMeshHoles(void *meshliteContext, int meshId); int fixMeshHoles(void *meshliteContext, int meshId);

View File

@ -640,6 +640,7 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::set<QUu
part["xMirrored"] = partIt.second.xMirrored ? "true" : "false"; part["xMirrored"] = partIt.second.xMirrored ? "true" : "false";
part["zMirrored"] = partIt.second.zMirrored ? "true" : "false"; part["zMirrored"] = partIt.second.zMirrored ? "true" : "false";
part["rounded"] = partIt.second.rounded ? "true" : "false"; part["rounded"] = partIt.second.rounded ? "true" : "false";
part["inverse"] = partIt.second.inverse ? "true" : "false";
if (partIt.second.hasColor) if (partIt.second.hasColor)
part["color"] = partIt.second.color.name(); part["color"] = partIt.second.color.name();
if (partIt.second.deformThicknessAdjusted()) if (partIt.second.deformThicknessAdjusted())
@ -732,6 +733,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
part.xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "xMirrored")); part.xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "xMirrored"));
part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored")); part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored"));
part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded")); part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded"));
part.inverse = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "inverse"));
const auto &colorIt = partKv.second.find("color"); const auto &colorIt = partKv.second.find("color");
if (colorIt != partKv.second.end()) { if (colorIt != partKv.second.end()) {
part.color = QColor(colorIt->second); part.color = QColor(colorIt->second);
@ -1210,6 +1212,20 @@ void SkeletonDocument::setPartVisibleState(QUuid partId, bool visible)
emit partVisibleStateChanged(partId); 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) void SkeletonDocument::setPartSubdivState(QUuid partId, bool subdived)
{ {
auto part = partMap.find(partId); auto part = partMap.find(partId);
@ -1238,6 +1254,96 @@ void SkeletonDocument::setPartDisableState(QUuid partId, bool disabled)
emit skeletonChanged(); 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() void SkeletonDocument::settleOrigin()
{ {
if (originSettled()) if (originSettled())

View File

@ -86,6 +86,7 @@ public:
bool rounded; bool rounded;
QColor color; QColor color;
bool hasColor; bool hasColor;
bool inverse;
QImage preview; QImage preview;
std::vector<QUuid> nodeIds; std::vector<QUuid> nodeIds;
SkeletonPart(const QUuid &withId=QUuid()) : SkeletonPart(const QUuid &withId=QUuid()) :
@ -99,7 +100,8 @@ public:
deformWidth(1.0), deformWidth(1.0),
rounded(false), rounded(false),
color(Theme::white), color(Theme::white),
hasColor(false) hasColor(false),
inverse(false)
{ {
id = withId.isNull() ? QUuid::createUuid() : withId; id = withId.isNull() ? QUuid::createUuid() : withId;
} }
@ -148,6 +150,7 @@ public:
rounded = other.rounded; rounded = other.rounded;
color = other.color; color = other.color;
hasColor = other.hasColor; hasColor = other.hasColor;
inverse = other.inverse;
} }
}; };
@ -217,6 +220,7 @@ signals:
void partDeformWidthChanged(QUuid partId); void partDeformWidthChanged(QUuid partId);
void partRoundStateChanged(QUuid partId); void partRoundStateChanged(QUuid partId);
void partColorStateChanged(QUuid partId); void partColorStateChanged(QUuid partId);
void partInverseStateChanged(QUuid partId);
void cleanup(); void cleanup();
void originChanged(); void originChanged();
void xlockStateChanged(); void xlockStateChanged();
@ -314,6 +318,11 @@ public slots:
void setPartDeformWidth(QUuid partId, float width); void setPartDeformWidth(QUuid partId, float width);
void setPartRoundState(QUuid partId, bool rounded); void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color); 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 saveSnapshot();
void undo(); void undo();
void redo(); void redo();

View File

@ -123,6 +123,11 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
connect(this, &SkeletonPartWidget::setPartDeformWidth, m_document, &SkeletonDocument::setPartDeformWidth); connect(this, &SkeletonPartWidget::setPartDeformWidth, m_document, &SkeletonDocument::setPartDeformWidth);
connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState); connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState);
connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState); 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::checkPart, m_document, &SkeletonDocument::checkPart);
connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur); connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur);
connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur); connect(this, &SkeletonPartWidget::disableBackgroundBlur, m_document, &SkeletonDocument::disableBackgroundBlur);
@ -206,6 +211,9 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p
}); });
setContextMenuPolicy(Qt::CustomContextMenu); setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &SkeletonPartWidget::customContextMenuRequested, [=] {
emit checkPart(m_partId);
});
connect(this, &SkeletonPartWidget::customContextMenuRequested, this, &SkeletonPartWidget::showContextMenu); connect(this, &SkeletonPartWidget::customContextMenuRequested, this, &SkeletonPartWidget::showContextMenu);
updateAllButtons(); updateAllButtons();
@ -227,8 +235,6 @@ void SkeletonPartWidget::updateAllButtons()
void SkeletonPartWidget::showContextMenu(const QPoint &pos) void SkeletonPartWidget::showContextMenu(const QPoint &pos)
{ {
emit checkPart(m_partId);
QMenu contextMenu(this); QMenu contextMenu(this);
const SkeletonPart *part = m_document->findPart(m_partId); const SkeletonPart *part = m_document->findPart(m_partId);
@ -274,6 +280,8 @@ void SkeletonPartWidget::showContextMenu(const QPoint &pos)
}); });
contextMenu.addAction(&hideAllPartsAction); contextMenu.addAction(&hideAllPartsAction);
contextMenu.addSeparator();
QAction lockAllPartsAction(tr("Lock All Parts"), this); QAction lockAllPartsAction(tr("Lock All Parts"), this);
connect(&lockAllPartsAction, &QAction::triggered, [=]() { connect(&lockAllPartsAction, &QAction::triggered, [=]() {
for (const auto &it: m_document->partIds) { for (const auto &it: m_document->partIds) {
@ -290,10 +298,51 @@ void SkeletonPartWidget::showContextMenu(const QPoint &pos)
}); });
contextMenu.addAction(&unlockAllPartsAction); 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)); contextMenu.exec(mapToGlobal(pos));
} }
void SkeletonPartWidget::updateCheckedState(bool checked) void SkeletonPartWidget::updateCheckedState(bool checked)
{ {
if (checked) if (checked)

View File

@ -21,6 +21,11 @@ signals:
void setPartDeformWidth(QUuid partId, float width); void setPartDeformWidth(QUuid partId, float width);
void setPartRoundState(QUuid partId, bool rounded); void setPartRoundState(QUuid partId, bool rounded);
void setPartColorState(QUuid partId, bool hasColor, QColor color); 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 checkPart(QUuid partId);
void groupOperationAdded(); void groupOperationAdded();
void enableBackgroundBlur(); void enableBackgroundBlur();