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
parent
35635534c2
commit
22910028e9
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue