diff --git a/docs/shortcuts.rst b/docs/shortcuts.rst index e90e0812..0f88bd63 100644 --- a/docs/shortcuts.rst +++ b/docs/shortcuts.rst @@ -66,6 +66,8 @@ Keyboard +----------------------+--------------------------------------------------------------------------+ | U | Toggle Part End Roundable | +----------------------+--------------------------------------------------------------------------+ +| W | Toggle Part Wrap Status: (W)rap using Convex hull/Normal | ++----------------------+--------------------------------------------------------------------------+ | TAB | Swith the Selected Nodes to Different Profile (Main / Side) | +----------------------+--------------------------------------------------------------------------+ diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index d75efb4d..b86184ce 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -196,6 +196,7 @@ void *MeshGenerator::combinePartMesh(QString partId) bool isDisabled = isTrueValueString(valueOfKeyInMapOrEmpty(part, "disabled")); bool xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(part, "xMirrored")); bool subdived = isTrueValueString(valueOfKeyInMapOrEmpty(part, "subdived")); + bool wrapped = isTrueValueString(valueOfKeyInMapOrEmpty(part, "wrapped")); int bmeshId = meshlite_bmesh_create(m_meshliteContext); if (subdived) meshlite_bmesh_set_cut_subdiv_count(m_meshliteContext, bmeshId, 1); @@ -288,14 +289,21 @@ void *MeshGenerator::combinePartMesh(QString partId) if (!bmeshToNodeIdMap.empty()) { meshId = meshlite_bmesh_generate_mesh(m_meshliteContext, bmeshId); loadVertexSources(m_meshliteContext, meshId, partIdNotAsString, bmeshToNodeIdMap, cacheBmeshVertices); - resultMesh = convertToCombinableMesh(m_meshliteContext, meshlite_triangulate(m_meshliteContext, meshId)); + if (wrapped) + resultMesh = convertToCombinableConvexHullMesh(m_meshliteContext, meshId); + else + resultMesh = convertToCombinableMesh(m_meshliteContext, meshlite_triangulate(m_meshliteContext, meshId)); } if (nullptr != resultMesh) { if (xMirrored) { int xMirroredMeshId = meshlite_mirror_in_x(m_meshliteContext, meshId, 0); loadVertexSources(m_meshliteContext, xMirroredMeshId, mirroredPartIdNotAsString, bmeshToNodeIdMap, cacheBmeshVertices); - void *mirroredMesh = convertToCombinableMesh(m_meshliteContext, meshlite_triangulate(m_meshliteContext, xMirroredMeshId)); + void *mirroredMesh = nullptr; + if (wrapped) + mirroredMesh = convertToCombinableConvexHullMesh(m_meshliteContext, xMirroredMeshId); + else + mirroredMesh = convertToCombinableMesh(m_meshliteContext, meshlite_triangulate(m_meshliteContext, xMirroredMeshId)); if (nullptr != mirroredMesh) { void *newResultMesh = unionCombinableMeshs(resultMesh, mirroredMesh); deleteCombinableMesh(mirroredMesh); diff --git a/src/meshutil.cpp b/src/meshutil.cpp index fd45aafc..435dd9ed 100644 --- a/src/meshutil.cpp +++ b/src/meshutil.cpp @@ -23,6 +23,7 @@ #include #include #include +#include typedef CGAL::Exact_predicates_exact_constructions_kernel ExactKernel; typedef CGAL::Simple_cartesian SimpleKernel; @@ -132,6 +133,34 @@ int makeMeshliteMeshFromCgal(void *meshlite, typename CGAL::Surface_mesh +ExactMesh *makeCgalConvexHullMeshFromMeshlite(void *meshlite, int meshId) +{ + typename CGAL::Surface_mesh *mesh = new typename CGAL::Surface_mesh; + int vertexCount = meshlite_get_vertex_count(meshlite, meshId); + float *vertexPositions = new float[vertexCount * 3]; + int vertexArrayLen = meshlite_get_vertex_position_array(meshlite, meshId, vertexPositions, vertexCount * 3); + int offset = 0; + assert(vertexArrayLen == vertexCount * 3); + std::vector points; + for (int i = 0; i < vertexCount; i++) { + float x = vertexPositions[offset + 0]; + float y = vertexPositions[offset + 1]; + float z = vertexPositions[offset + 2]; + if (std::isnan(x) || std::isinf(x)) + x = 0; + if (std::isnan(y) || std::isinf(y)) + y = 0; + if (std::isnan(z) || std::isinf(z)) + z = 0; + points.push_back(typename Kernel::Point_3(x, y, z)); + offset += 3; + } + delete[] vertexPositions; + CGAL::convex_hull_3(points.begin(), points.end(), *mesh); + return mesh; +} + ExactMesh *unionCgalMeshs(ExactMesh *first, ExactMesh *second) { ExactMesh *mesh = new ExactMesh; @@ -315,3 +344,8 @@ void *cloneCombinableMesh(void *mesh) return (void *)new ExactMesh(*(ExactMesh *)mesh); } +void *convertToCombinableConvexHullMesh(void *meshliteContext, int meshId) +{ + ExactMesh *mesh = makeCgalConvexHullMeshFromMeshlite(meshliteContext, meshId); + return (void *)mesh; +} diff --git a/src/meshutil.h b/src/meshutil.h index 106dc9e7..afdafde6 100644 --- a/src/meshutil.h +++ b/src/meshutil.h @@ -15,5 +15,6 @@ void *diffCombinableMeshs(void *first, void *second); int convertFromCombinableMesh(void *meshliteContext, void *mesh); void deleteCombinableMesh(void *mesh); void *cloneCombinableMesh(void *mesh); +void *convertToCombinableConvexHullMesh(void *meshliteContext, int meshId); #endif diff --git a/src/skeletondocument.cpp b/src/skeletondocument.cpp index d483e5ea..675c3151 100644 --- a/src/skeletondocument.cpp +++ b/src/skeletondocument.cpp @@ -661,6 +661,7 @@ void SkeletonDocument::toSnapshot(SkeletonSnapshot *snapshot, const std::setsecond.wrapped == wrapped) + return; + part->second.wrapped = wrapped; + part->second.dirty = true; + emit partWrapStateChanged(partId); + emit skeletonChanged(); +} + void SkeletonDocument::setPartColorState(QUuid partId, bool hasColor, QColor color) { auto part = partMap.find(partId); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 221c6d66..a8970798 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -87,6 +87,7 @@ public: QUuid componentId; std::vector nodeIds; bool dirty; + bool wrapped; SkeletonPart(const QUuid &withId=QUuid()) : visible(true), locked(false), @@ -99,7 +100,8 @@ public: rounded(false), color(Theme::white), hasColor(false), - dirty(true) + dirty(true), + wrapped(false) { id = withId.isNull() ? QUuid::createUuid() : withId; } @@ -148,6 +150,7 @@ public: rounded = other.rounded; color = other.color; hasColor = other.hasColor; + wrapped = other.wrapped; componentId = other.componentId; } }; @@ -328,6 +331,7 @@ signals: void partDeformWidthChanged(QUuid partId); void partRoundStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); + void partWrapStateChanged(QUuid partId); void componentInverseStateChanged(QUuid partId); void cleanup(); void originChanged(); @@ -425,6 +429,7 @@ public slots: void setPartDeformWidth(QUuid partId, float width); void setPartRoundState(QUuid partId, bool rounded); void setPartColorState(QUuid partId, bool hasColor, QColor color); + void setPartWrapState(QUuid partId, bool wrapped); void setComponentInverseState(QUuid componentId, bool inverse); void moveComponentUp(QUuid componentId); void moveComponentDown(QUuid componentId); diff --git a/src/skeletondocumentwindow.cpp b/src/skeletondocumentwindow.cpp index 4dd8914c..5181640b 100644 --- a/src/skeletondocumentwindow.cpp +++ b/src/skeletondocumentwindow.cpp @@ -547,6 +547,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(graphicsWidget, &SkeletonGraphicsWidget::setPartDisableState, m_document, &SkeletonDocument::setPartDisableState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartXmirrorState, m_document, &SkeletonDocument::setPartXmirrorState); connect(graphicsWidget, &SkeletonGraphicsWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState); + connect(graphicsWidget, &SkeletonGraphicsWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState); connect(graphicsWidget, &SkeletonGraphicsWidget::setXlockState, m_document, &SkeletonDocument::setXlockState); connect(graphicsWidget, &SkeletonGraphicsWidget::setYlockState, m_document, &SkeletonDocument::setYlockState); @@ -614,6 +615,7 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() : connect(m_document, &SkeletonDocument::partDeformThicknessChanged, partTreeWidget, &SkeletonPartTreeWidget::partDeformChanged); connect(m_document, &SkeletonDocument::partDeformWidthChanged, partTreeWidget, &SkeletonPartTreeWidget::partDeformChanged); connect(m_document, &SkeletonDocument::partRoundStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partRoundStateChanged); + connect(m_document, &SkeletonDocument::partWrapStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partWrapStateChanged); connect(m_document, &SkeletonDocument::partColorStateChanged, partTreeWidget, &SkeletonPartTreeWidget::partColorStateChanged); connect(m_document, &SkeletonDocument::partRemoved, partTreeWidget, &SkeletonPartTreeWidget::partRemoved); connect(m_document, &SkeletonDocument::cleanup, partTreeWidget, &SkeletonPartTreeWidget::removeAllContent); diff --git a/src/skeletongraphicswidget.cpp b/src/skeletongraphicswidget.cpp index af7ea378..9d0744a9 100644 --- a/src/skeletongraphicswidget.cpp +++ b/src/skeletongraphicswidget.cpp @@ -1502,6 +1502,14 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event) emit groupOperationAdded(); return true; } + } else if (event->key() == Qt::Key_W) { + if (SkeletonDocumentEditMode::Select == m_document->editMode && !m_lastCheckedPart.isNull()) { + const SkeletonPart *part = m_document->findPart(m_lastCheckedPart); + bool partWrapped = part && part->wrapped; + emit setPartWrapState(m_lastCheckedPart, !partWrapped); + emit groupOperationAdded(); + return true; + } } return false; } diff --git a/src/skeletongraphicswidget.h b/src/skeletongraphicswidget.h index a3e963d3..86ed3426 100644 --- a/src/skeletongraphicswidget.h +++ b/src/skeletongraphicswidget.h @@ -379,6 +379,7 @@ signals: void setPartDisableState(QUuid partId, bool disabled); void setPartXmirrorState(QUuid partId, bool mirrored); void setPartRoundState(QUuid partId, bool rounded); + void setPartWrapState(QUuid partId, bool wrapped); void setXlockState(bool locked); void setYlockState(bool locked); void setZlockState(bool locked); diff --git a/src/skeletonparttreewidget.cpp b/src/skeletonparttreewidget.cpp index 2ed293bb..3a33c5f7 100644 --- a/src/skeletonparttreewidget.cpp +++ b/src/skeletonparttreewidget.cpp @@ -561,6 +561,17 @@ void SkeletonPartTreeWidget::partRoundStateChanged(QUuid partId) widget->updateRoundButton(); } +void SkeletonPartTreeWidget::partWrapStateChanged(QUuid partId) +{ + auto item = m_partItemMap.find(partId); + if (item == m_partItemMap.end()) { + qDebug() << "Part item not found:" << partId; + return; + } + SkeletonPartWidget *widget = (SkeletonPartWidget *)itemWidget(item->second, 0); + widget->updateWrapButton(); +} + void SkeletonPartTreeWidget::partColorStateChanged(QUuid partId) { auto item = m_partItemMap.find(partId); diff --git a/src/skeletonparttreewidget.h b/src/skeletonparttreewidget.h index 5dd1866a..9e0290cd 100644 --- a/src/skeletonparttreewidget.h +++ b/src/skeletonparttreewidget.h @@ -53,6 +53,7 @@ public slots: void partXmirrorStateChanged(QUuid partId); void partDeformChanged(QUuid partId); void partRoundStateChanged(QUuid partId); + void partWrapStateChanged(QUuid partId); void partColorStateChanged(QUuid partId); void partChecked(QUuid partId); void partUnchecked(QUuid partId); diff --git a/src/skeletonpartwidget.cpp b/src/skeletonpartwidget.cpp index 52bbae86..c49cee35 100644 --- a/src/skeletonpartwidget.cpp +++ b/src/skeletonpartwidget.cpp @@ -48,6 +48,10 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p m_colorButton->setSizePolicy(retainSizePolicy); initButton(m_colorButton); + m_wrapButton = new QPushButton; + m_wrapButton->setSizePolicy(retainSizePolicy); + initButton(m_wrapButton); + m_previewLabel = new QLabel; m_previewLabel->setFixedSize(Theme::previewImageSize, Theme::previewImageSize); @@ -68,9 +72,9 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p toolsLayout->setContentsMargins(0, 0, 5, 0); int row = 0; int col = 0; - toolsLayout->addWidget(m_visibleButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_lockButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_disableButton, row, col++, Qt::AlignBottom); + toolsLayout->addWidget(m_wrapButton, row, col++, Qt::AlignBottom); toolsLayout->addWidget(m_colorButton, row, col++, Qt::AlignBottom); row++; col = 0; @@ -79,17 +83,21 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p toolsLayout->addWidget(m_xMirrorButton, row, col++, Qt::AlignTop); toolsLayout->addWidget(m_roundButton, row, col++, Qt::AlignTop); + m_visibleButton->setContentsMargins(0, 0, 0, 0); + QHBoxLayout *previewAndToolsLayout = new QHBoxLayout; previewAndToolsLayout->setSpacing(0); previewAndToolsLayout->setContentsMargins(0, 0, 0, 0); + previewAndToolsLayout->addWidget(m_visibleButton); previewAndToolsLayout->addWidget(m_previewLabel); previewAndToolsLayout->addLayout(toolsLayout); previewAndToolsLayout->setStretch(0, 0); previewAndToolsLayout->setStretch(1, 0); + previewAndToolsLayout->setStretch(2, 1); QWidget *backgroundWidget = new QWidget; backgroundWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - backgroundWidget->setFixedSize(Theme::previewImageSize + Theme::miniIconSize * 4 + 5, Theme::previewImageSize); + backgroundWidget->setFixedSize(preferredSize().width(), Theme::previewImageSize); backgroundWidget->setObjectName("background"); m_backgroundWidget = backgroundWidget; backgroundWidget->setLayout(previewAndToolsLayout); @@ -119,6 +127,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p connect(this, &SkeletonPartWidget::setPartDeformThickness, m_document, &SkeletonDocument::setPartDeformThickness); connect(this, &SkeletonPartWidget::setPartDeformWidth, m_document, &SkeletonDocument::setPartDeformWidth); connect(this, &SkeletonPartWidget::setPartRoundState, m_document, &SkeletonDocument::setPartRoundState); + connect(this, &SkeletonPartWidget::setPartWrapState, m_document, &SkeletonDocument::setPartWrapState); connect(this, &SkeletonPartWidget::setPartColorState, m_document, &SkeletonDocument::setPartColorState); connect(this, &SkeletonPartWidget::checkPart, m_document, &SkeletonDocument::checkPart); connect(this, &SkeletonPartWidget::enableBackgroundBlur, m_document, &SkeletonDocument::enableBackgroundBlur); @@ -202,6 +211,16 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p showColorSettingPopup(mapFromGlobal(QCursor::pos())); }); + connect(m_wrapButton, &QPushButton::clicked, [=]() { + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + emit setPartWrapState(m_partId, !part->wrapped); + emit groupOperationAdded(); + }); + setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setFixedSize(preferredSize()); @@ -210,7 +229,7 @@ SkeletonPartWidget::SkeletonPartWidget(const SkeletonDocument *document, QUuid p QSize SkeletonPartWidget::preferredSize() { - return QSize(Theme::previewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::previewImageSize + 6); + return QSize(Theme::miniIconSize + Theme::previewImageSize + Theme::miniIconSize * 4 + 5 + 2, Theme::previewImageSize + 6); } void SkeletonPartWidget::updateAllButtons() @@ -223,6 +242,7 @@ void SkeletonPartWidget::updateAllButtons() updateDeformButton(); updateRoundButton(); updateColorButton(); + updateWrapButton(); } void SkeletonPartWidget::updateCheckedState(bool checked) @@ -505,15 +525,21 @@ void SkeletonPartWidget::updateColorButton() updateButton(m_colorButton, QChar(fa::eyedropper), false); } +void SkeletonPartWidget::updateWrapButton() +{ + const SkeletonPart *part = m_document->findPart(m_partId); + if (!part) { + qDebug() << "Part not found:" << m_partId; + return; + } + if (part->wrapped) + updateButton(m_wrapButton, QChar(fa::cube), true); + else + updateButton(m_wrapButton, QChar(fa::cube), false); +} + void SkeletonPartWidget::reload() { updatePreview(); - updateLockButton(); - updateVisibleButton(); - updateSubdivButton(); - updateDisableButton(); - updateXmirrorButton(); - updateDeformButton(); - updateRoundButton(); - updateColorButton(); + updateAllButtons(); } diff --git a/src/skeletonpartwidget.h b/src/skeletonpartwidget.h index 6e5f92ad..af2b9387 100644 --- a/src/skeletonpartwidget.h +++ b/src/skeletonpartwidget.h @@ -19,7 +19,7 @@ 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 setPartWrapState(QUuid partId, bool wrapped); void movePartUp(QUuid partId); void movePartDown(QUuid partId); void movePartToTop(QUuid partId); @@ -41,6 +41,7 @@ public: void updateDeformButton(); void updateRoundButton(); void updateColorButton(); + void updateWrapButton(); void updateCheckedState(bool checked); static QSize preferredSize(); protected: @@ -62,6 +63,7 @@ private: QPushButton *m_deformButton; QPushButton *m_roundButton; QPushButton *m_colorButton; + QPushButton *m_wrapButton; QLabel *m_nameLabel; QWidget *m_backgroundWidget; private: diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll index 2299ce4c..b1db5776 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll and b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib index 340aafd5..427ac7f9 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib and b/thirdparty/meshlite/meshlite_unstable_vc14_x64/meshlite.dll.lib differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll index d3681cb7..38299c65 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll and b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll differ diff --git a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib index 9c300c86..38a37bdd 100644 Binary files a/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib and b/thirdparty/meshlite/meshlite_unstable_vc14_x86/meshlite.dll.lib differ