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
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -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.

View File

@ -348,6 +348,7 @@ void MeshGenerator::process()
std::vector<int> meshIds;
std::vector<int> subdivMeshIds;
std::set<int> 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)

View File

@ -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<int> &meshIds, int *errorCount)
int unionMeshs(void *meshliteContext, const std::vector<int> &meshIds, const std::set<int> &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<int> &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)

View File

@ -1,9 +1,10 @@
#ifndef MESH_UTIL_H
#define MESH_UTIL_H
#include <vector>
#include <set>
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 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["zMirrored"] = partIt.second.zMirrored ? "true" : "false";
part["rounded"] = partIt.second.rounded ? "true" : "false";
part["inverse"] = partIt.second.inverse ? "true" : "false";
if (partIt.second.hasColor)
part["color"] = partIt.second.color.name();
if (partIt.second.deformThicknessAdjusted())
@ -732,6 +733,7 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
part.xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "xMirrored"));
part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored"));
part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded"));
part.inverse = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "inverse"));
const auto &colorIt = partKv.second.find("color");
if (colorIt != partKv.second.end()) {
part.color = QColor(colorIt->second);
@ -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())

View File

@ -86,6 +86,7 @@ public:
bool rounded;
QColor color;
bool hasColor;
bool inverse;
QImage preview;
std::vector<QUuid> 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();

View File

@ -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) {
@ -290,10 +298,51 @@ 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)

View File

@ -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();