diff --git a/dust3d.pro b/dust3d.pro index e094470f..5e548f07 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -298,6 +298,9 @@ HEADERS += src/cutface.h SOURCES += src/parttarget.cpp HEADERS += src/parttarget.h +SOURCES += src/partbase.cpp +HEADERS += src/partbase.h + SOURCES += src/cutfacewidget.cpp HEADERS += src/cutfacewidget.h diff --git a/src/document.cpp b/src/document.cpp index 86236b01..74e2891f 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -893,7 +893,10 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set &limitNodeId part["subdived"] = partIt.second.subdived ? "true" : "false"; part["disabled"] = partIt.second.disabled ? "true" : "false"; part["xMirrored"] = partIt.second.xMirrored ? "true" : "false"; - part["zMirrored"] = partIt.second.zMirrored ? "true" : "false"; + if (partIt.second.zMirrored) + part["zMirrored"] = partIt.second.zMirrored ? "true" : "false"; + if (partIt.second.base != PartBase::XYZ) + part["base"] = PartBaseToString(partIt.second.base); part["rounded"] = partIt.second.rounded ? "true" : "false"; part["chamfered"] = partIt.second.chamfered ? "true" : "false"; if (PartTarget::Model != partIt.second.target) @@ -1163,6 +1166,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste) part.disabled = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "disabled")); part.xMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "xMirrored")); part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored")); + part.base = PartBaseFromString(valueOfKeyInMapOrEmpty(partKv.second, "base").toUtf8().constData()); part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded")); part.chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "chamfered")); part.target = PartTargetFromString(valueOfKeyInMapOrEmpty(partKv.second, "target").toUtf8().constData()); @@ -2219,6 +2223,21 @@ void Document::setPartDeformThickness(QUuid partId, float thickness) emit skeletonChanged(); } +void Document::setPartBase(QUuid partId, PartBase base) +{ + auto part = partMap.find(partId); + if (part == partMap.end()) { + qDebug() << "Part not found:" << partId; + return; + } + if (part->second.base == base) + return; + part->second.base = base; + part->second.dirty = true; + emit partBaseChanged(partId); + emit skeletonChanged(); +} + void Document::setPartDeformWidth(QUuid partId, float width) { auto part = partMap.find(partId); diff --git a/src/document.h b/src/document.h index abf1d414..9b970ee2 100644 --- a/src/document.h +++ b/src/document.h @@ -396,6 +396,7 @@ signals: void partDisableStateChanged(QUuid partId); void partXmirrorStateChanged(QUuid partId); //void partZmirrorStateChanged(QUuid partId); + void partBaseChanged(QUuid partId); void partDeformThicknessChanged(QUuid partId); void partDeformWidthChanged(QUuid partId); void partRoundStateChanged(QUuid partId); @@ -553,6 +554,7 @@ public slots: void setPartDisableState(QUuid partId, bool disabled); void setPartXmirrorState(QUuid partId, bool mirrored); //void setPartZmirrorState(QUuid partId, bool mirrored); + void setPartBase(QUuid partId, PartBase base); void setPartDeformThickness(QUuid partId, float thickness); void setPartDeformWidth(QUuid partId, float width); void setPartRoundState(QUuid partId, bool rounded); diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index f81179a3..bb2794be 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -832,6 +832,7 @@ DocumentWindow::DocumentWindow() : connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState); connect(partTreeWidget, &PartTreeWidget::setComponentCombineMode, m_document, &Document::setComponentCombineMode); connect(partTreeWidget, &PartTreeWidget::setPartTarget, m_document, &Document::setPartTarget); + connect(partTreeWidget, &PartTreeWidget::setPartBase, m_document, &Document::setPartBase); connect(partTreeWidget, &PartTreeWidget::hideDescendantComponents, m_document, &Document::hideDescendantComponents); connect(partTreeWidget, &PartTreeWidget::showDescendantComponents, m_document, &Document::showDescendantComponents); connect(partTreeWidget, &PartTreeWidget::lockDescendantComponents, m_document, &Document::lockDescendantComponents); diff --git a/src/meshgenerator.cpp b/src/meshgenerator.cpp index 011b1e41..c83a36a2 100644 --- a/src/meshgenerator.cpp +++ b/src/meshgenerator.cpp @@ -13,6 +13,7 @@ #include "cutface.h" #include "parttarget.h" #include "theme.h" +#include "partbase.h" MeshGenerator::MeshGenerator(Snapshot *snapshot) : m_snapshot(snapshot) @@ -171,6 +172,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt float deformWidth = 1.0; float cutRotation = 0.0; auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData()); + auto base = PartBaseFromString(valueOfKeyInMapOrEmpty(part, "base").toUtf8().constData()); std::vector cutTemplate; QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace"); @@ -441,6 +443,13 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt builder->setDeformThickness(deformThickness); builder->setDeformWidth(deformWidth); builder->setCutRotation(cutRotation); + if (PartBase::YZ == base) { + builder->enableBaseNormalOnX(false); + } else if (PartBase::XY == base) { + builder->enableBaseNormalOnZ(false); + } else if (PartBase::ZX == base) { + builder->enableBaseNormalOnY(false); + } for (const auto &node: modifier->nodes()) builder->addNode(node.position, node.radius, node.cutTemplate); diff --git a/src/partbase.cpp b/src/partbase.cpp new file mode 100644 index 00000000..9af066f8 --- /dev/null +++ b/src/partbase.cpp @@ -0,0 +1,5 @@ +#include "partbase.h" + +IMPL_PartBaseFromString +IMPL_PartBaseToString +IMPL_PartBaseToDispName \ No newline at end of file diff --git a/src/partbase.h b/src/partbase.h new file mode 100644 index 00000000..b80aea08 --- /dev/null +++ b/src/partbase.h @@ -0,0 +1,64 @@ +#ifndef DUST3D_PART_BASE_H +#define DUST3D_PART_BASE_H +#include +#include + +enum class PartBase +{ + XYZ = 0, + YZ, + XY, + ZX, + Count +}; +PartBase PartBaseFromString(const char *baseString); +#define IMPL_PartBaseFromString \ +PartBase PartBaseFromString(const char *baseString) \ +{ \ + QString base = baseString; \ + if (base == "XYZ") \ + return PartBase::XYZ; \ + if (base == "YZ") \ + return PartBase::YZ; \ + if (base == "XY") \ + return PartBase::XY; \ + if (base == "ZX") \ + return PartBase::ZX; \ + return PartBase::XYZ; \ +} +const char *PartBaseToString(PartBase base); +#define IMPL_PartBaseToString \ +const char *PartBaseToString(PartBase base) \ +{ \ + switch (base) { \ + case PartBase::XYZ: \ + return "XYZ"; \ + case PartBase::YZ: \ + return "YZ"; \ + case PartBase::XY: \ + return "XY"; \ + case PartBase::ZX: \ + return "ZX"; \ + default: \ + return "XYZ"; \ + } \ +} +QString PartBaseToDispName(PartBase base); +#define IMPL_PartBaseToDispName \ +QString PartBaseToDispName(PartBase base) \ +{ \ + switch (base) { \ + case PartBase::XYZ: \ + return QObject::tr("Dynamic"); \ + case PartBase::YZ: \ + return QObject::tr("Side Plane"); \ + case PartBase::XY: \ + return QObject::tr("Front Plane"); \ + case PartBase::ZX: \ + return QObject::tr("Top Plane"); \ + default: \ + return QObject::tr("Dynamic"); \ + } \ +} + +#endif diff --git a/src/parttreewidget.cpp b/src/parttreewidget.cpp index 57ef186e..52f81a13 100644 --- a/src/parttreewidget.cpp +++ b/src/parttreewidget.cpp @@ -294,6 +294,19 @@ void PartTreeWidget::showContextMenu(const QPoint &pos) }); } + QComboBox *partBaseSelectBox = nullptr; + if (nullptr != part && nullptr != partWidget) { + partBaseSelectBox = new QComboBox; + for (size_t i = 0; i < (size_t)PartBase::Count; ++i) { + PartBase base = (PartBase)i; + partBaseSelectBox->addItem(PartBaseToDispName(base)); + } + partBaseSelectBox->setCurrentIndex((int)part->base); + connect(partBaseSelectBox, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { + emit setPartBase(part->id, (PartBase)index); + }); + } + //QHBoxLayout *combineModeLayout = new QHBoxLayout; //combineModeLayout->setAlignment(Qt::AlignCenter); //combineModeLayout->setContentsMargins(0, 0, 0, 0); @@ -301,6 +314,8 @@ void PartTreeWidget::showContextMenu(const QPoint &pos) //combineModeLayout->addWidget(combineModeSelectBox); QFormLayout *componentSettingsLayout = new QFormLayout; + if (nullptr != partBaseSelectBox) + componentSettingsLayout->addRow(tr("Base"), partBaseSelectBox); if (nullptr != partTargetSelectBox) componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox); componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox); diff --git a/src/parttreewidget.h b/src/parttreewidget.h index 20125617..eb6a48ea 100644 --- a/src/parttreewidget.h +++ b/src/parttreewidget.h @@ -22,6 +22,7 @@ signals: void setComponentSmoothAll(QUuid componentId, float toSmoothAll); void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam); void setPartTarget(QUuid partId, PartTarget target); + void setPartBase(QUuid partId, PartBase base); void moveComponent(QUuid componentId, QUuid toParentId); void removeComponent(QUuid componentId); void hideOtherComponents(QUuid componentId); diff --git a/src/skeletondocument.h b/src/skeletondocument.h index 8b971217..48bb7419 100644 --- a/src/skeletondocument.h +++ b/src/skeletondocument.h @@ -11,6 +11,7 @@ #include "meshloader.h" #include "cutface.h" #include "parttarget.h" +#include "partbase.h" class SkeletonNode { @@ -77,6 +78,7 @@ public: bool disabled; bool xMirrored; bool zMirrored; + PartBase base; float deformThickness; float deformWidth; bool rounded; @@ -98,6 +100,7 @@ public: disabled(false), xMirrored(false), zMirrored(false), + base(PartBase::XYZ), deformThickness(1.0), deformWidth(1.0), rounded(false), @@ -189,6 +192,7 @@ public: disabled = other.disabled; xMirrored = other.xMirrored; zMirrored = other.zMirrored; + base = other.base; deformThickness = other.deformThickness; deformWidth = other.deformWidth; rounded = other.rounded; diff --git a/thirdparty/nodemesh/nodemesh/builder.cpp b/thirdparty/nodemesh/nodemesh/builder.cpp index f4f1683c..48af2a9c 100644 --- a/thirdparty/nodemesh/nodemesh/builder.cpp +++ b/thirdparty/nodemesh/nodemesh/builder.cpp @@ -289,10 +289,45 @@ bool Builder::validateNormal(const QVector3D &normal) return true; } -std::pair Builder::calculateBaseNormal(const std::vector &directs, - const std::vector &positions, +void Builder::enableBaseNormalOnX(bool enabled) +{ + m_baseNormalOnX = enabled; +} + +void Builder::enableBaseNormalOnY(bool enabled) +{ + m_baseNormalOnY = enabled; +} + +void Builder::enableBaseNormalOnZ(bool enabled) +{ + m_baseNormalOnZ = enabled; +} + +std::pair Builder::calculateBaseNormal(const std::vector &inputDirects, + const std::vector &inputPositions, const std::vector &weights) { + std::vector directs = inputDirects; + std::vector positions = inputPositions; + if (!m_baseNormalOnX || !m_baseNormalOnY || !m_baseNormalOnZ) { + for (auto &it: directs) { + if (!m_baseNormalOnX) + it.setX(0); + if (!m_baseNormalOnY) + it.setY(0); + if (!m_baseNormalOnZ) + it.setZ(0); + } + for (auto &it: positions) { + if (!m_baseNormalOnX) + it.setX(0); + if (!m_baseNormalOnY) + it.setY(0); + if (!m_baseNormalOnZ) + it.setZ(0); + } + } auto calculateTwoPointsNormal = [&](size_t i0, size_t i1) -> std::pair { auto normal = QVector3D::crossProduct(directs[i0], directs[i1]).normalized(); if (validateNormal(normal)) { diff --git a/thirdparty/nodemesh/nodemesh/builder.h b/thirdparty/nodemesh/nodemesh/builder.h index 79893957..2b650003 100644 --- a/thirdparty/nodemesh/nodemesh/builder.h +++ b/thirdparty/nodemesh/nodemesh/builder.h @@ -17,6 +17,9 @@ public: void setDeformThickness(float thickness); void setDeformWidth(float width); void setCutRotation(float cutRotation); + void enableBaseNormalOnX(bool enabled); + void enableBaseNormalOnY(bool enabled); + void enableBaseNormalOnZ(bool enabled); const std::vector &generatedVertices(); const std::vector> &generatedFaces(); const std::vector &generatedVerticesSourceNodeIndices(); @@ -92,11 +95,14 @@ private: float m_deformThickness = 1.0; float m_deformWidth = 1.0; float m_cutRotation = 0.0; + bool m_baseNormalOnX = true; + bool m_baseNormalOnY = true; + bool m_baseNormalOnZ = true; void sortNodeIndices(); void prepareNode(size_t nodeIndex); - std::pair calculateBaseNormal(const std::vector &directs, - const std::vector &positions, + std::pair calculateBaseNormal(const std::vector &inputDirects, + const std::vector &inputPositions, const std::vector &weights); bool validateNormal(const QVector3D &normal); void resolveBaseNormalRecursively(size_t nodeIndex);