Add user defined cut face
Extruding shape is totally customizable now, it's not limit to predefined shapes.master
parent
fc341986c0
commit
8f5368a43f
|
@ -295,6 +295,15 @@ HEADERS += src/cutdocument.h
|
|||
SOURCES += src/cutface.cpp
|
||||
HEADERS += src/cutface.h
|
||||
|
||||
SOURCES += src/parttarget.cpp
|
||||
HEADERS += src/parttarget.h
|
||||
|
||||
SOURCES += src/cutfacewidget.cpp
|
||||
HEADERS += src/cutfacewidget.h
|
||||
|
||||
SOURCES += src/cutfacelistwidget.cpp
|
||||
HEADERS += src/cutfacelistwidget.h
|
||||
|
||||
SOURCES += src/remoteioserver.cpp
|
||||
HEADERS += src/remoteioserver.h
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ void main()
|
|||
vert = (modelMatrix * vertex).xyz;
|
||||
vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz);
|
||||
vertColor = color;
|
||||
cameraPos = vec3(0, 0, -2.1);
|
||||
cameraPos = vec3(0, 0, -4.0);
|
||||
|
||||
firstLightPos = vec3(5.0, 5.0, 5.0);
|
||||
secondLightPos = vec3(-5.0, 5.0, 5.0);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
#include <QStringList>
|
||||
#include <QVector2D>
|
||||
#include <QVector3D>
|
||||
#include "cutface.h"
|
||||
|
||||
IMPL_CutFaceFromString
|
||||
|
@ -38,3 +40,45 @@ void normalizeCutFacePoints(std::vector<QVector2D> *points)
|
|||
position.setY((position.y() - yMiddle) * 2 / longSize);
|
||||
}
|
||||
}
|
||||
|
||||
void cutFacePointsFromNodes(std::vector<QVector2D> &points, const std::vector<std::tuple<float, float, float>> &nodes)
|
||||
{
|
||||
if (nodes.size() < 2)
|
||||
return;
|
||||
std::vector<QVector2D> edges;
|
||||
for (size_t i = 1; i < nodes.size(); ++i) {
|
||||
const auto &previous = nodes[i - 1];
|
||||
const auto ¤t = nodes[i];
|
||||
edges.push_back((QVector2D(std::get<1>(current), std::get<2>(current)) -
|
||||
QVector2D(std::get<1>(previous), std::get<2>(previous))).normalized());
|
||||
}
|
||||
std::vector<QVector2D> nodeDirections;
|
||||
nodeDirections.push_back(edges[0]);
|
||||
for (size_t i = 1; i < nodes.size() - 1; ++i) {
|
||||
const auto &previousEdge = edges[i - 1];
|
||||
const auto &nextEdge = edges[i];
|
||||
nodeDirections.push_back((previousEdge + nextEdge).normalized());
|
||||
}
|
||||
nodeDirections.push_back(edges[edges.size() - 1]);
|
||||
std::vector<std::pair<QVector2D, QVector2D>> cutPoints;
|
||||
for (size_t i = 0; i < nodes.size(); ++i) {
|
||||
const auto ¤t = nodes[i];
|
||||
const auto &direction = nodeDirections[i];
|
||||
const auto &radius = std::get<0>(current);
|
||||
QVector3D origin = QVector3D(std::get<1>(current), std::get<2>(current), 0);
|
||||
QVector3D pointer = QVector3D(direction.x(), direction.y(), 0);
|
||||
QVector3D rotateAxis = QVector3D(0, 0, 1);
|
||||
QVector3D u = QVector3D::crossProduct(pointer, rotateAxis).normalized();
|
||||
QVector3D upPoint = origin + u * radius;
|
||||
QVector3D downPoint = origin - u * radius;
|
||||
cutPoints.push_back({QVector2D(upPoint.x(), upPoint.y()),
|
||||
QVector2D(downPoint.x(), downPoint.y())});
|
||||
}
|
||||
for (const auto &it: cutPoints) {
|
||||
points.push_back(it.first);
|
||||
}
|
||||
for (auto it = cutPoints.rbegin(); it != cutPoints.rend(); ++it) {
|
||||
points.push_back(it->second);
|
||||
}
|
||||
normalizeCutFacePoints(&points);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ enum class CutFace
|
|||
Pentagon,
|
||||
Hexagon,
|
||||
Triangle,
|
||||
//UserDefined,
|
||||
UserDefined,
|
||||
Count
|
||||
};
|
||||
|
||||
|
@ -28,6 +28,8 @@ CutFace CutFaceFromString(const char *faceString) \
|
|||
return CutFace::Hexagon; \
|
||||
if (face == "Triangle") \
|
||||
return CutFace::Triangle; \
|
||||
if (face == "UserDefined") \
|
||||
return CutFace::UserDefined; \
|
||||
return CutFace::Quad; \
|
||||
}
|
||||
QString CutFaceToString(CutFace cutFace);
|
||||
|
@ -43,6 +45,8 @@ QString CutFaceToString(CutFace cutFace) \
|
|||
return "Hexagon"; \
|
||||
case CutFace::Triangle: \
|
||||
return "Triangle"; \
|
||||
case CutFace::UserDefined: \
|
||||
return "UserDefined"; \
|
||||
default: \
|
||||
return ""; \
|
||||
} \
|
||||
|
@ -93,5 +97,6 @@ std::vector<QVector2D> CutFaceToPoints(CutFace cutFace) \
|
|||
}
|
||||
|
||||
void normalizeCutFacePoints(std::vector<QVector2D> *points);
|
||||
void cutFacePointsFromNodes(std::vector<QVector2D> &points, const std::vector<std::tuple<float, float, float>> &nodes);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
#include <QGuiApplication>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include "cutfacelistwidget.h"
|
||||
|
||||
CutFaceListWidget::CutFaceListWidget(const Document *document, QWidget *parent) :
|
||||
QTreeWidget(parent),
|
||||
m_document(document)
|
||||
{
|
||||
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
|
||||
setAutoScroll(false);
|
||||
|
||||
setHeaderHidden(true);
|
||||
|
||||
QPalette palette = this->palette();
|
||||
palette.setColor(QPalette::Window, Qt::transparent);
|
||||
palette.setColor(QPalette::Base, Qt::transparent);
|
||||
setPalette(palette);
|
||||
|
||||
setStyleSheet("QTreeView {qproperty-indentation: 0;}");
|
||||
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(this, &QTreeWidget::customContextMenuRequested, this, &CutFaceListWidget::showContextMenu);
|
||||
|
||||
std::set<QUuid> cutFacePartIds;
|
||||
for (const auto &it: m_document->partMap) {
|
||||
if (PartTarget::CutFace == it.second.target) {
|
||||
if (cutFacePartIds.find(it.first) != cutFacePartIds.end())
|
||||
continue;
|
||||
cutFacePartIds.insert(it.first);
|
||||
m_cutFacePartIdList.push_back(it.first);
|
||||
}
|
||||
if (!it.second.cutFaceLinkedId.isNull()) {
|
||||
if (cutFacePartIds.find(it.second.cutFaceLinkedId) != cutFacePartIds.end())
|
||||
continue;
|
||||
cutFacePartIds.insert(it.second.cutFaceLinkedId);
|
||||
m_cutFacePartIdList.push_back(it.second.cutFaceLinkedId);
|
||||
}
|
||||
}
|
||||
|
||||
reload();
|
||||
}
|
||||
|
||||
bool CutFaceListWidget::isEmpty()
|
||||
{
|
||||
return m_cutFacePartIdList.empty();
|
||||
}
|
||||
|
||||
void CutFaceListWidget::enableMultipleSelection(bool enabled)
|
||||
{
|
||||
m_multipleSelectionEnabled = enabled;
|
||||
}
|
||||
|
||||
void CutFaceListWidget::updateCutFaceSelectState(QUuid partId, bool selected)
|
||||
{
|
||||
auto findItemResult = m_itemMap.find(partId);
|
||||
if (findItemResult == m_itemMap.end()) {
|
||||
qDebug() << "Find part item failed:" << partId;
|
||||
return;
|
||||
}
|
||||
CutFaceWidget *cutFaceWidget = (CutFaceWidget *)itemWidget(findItemResult->second.first, findItemResult->second.second);
|
||||
cutFaceWidget->updateCheckedState(selected);
|
||||
}
|
||||
|
||||
void CutFaceListWidget::selectCutFace(QUuid partId, bool multiple)
|
||||
{
|
||||
if (multiple) {
|
||||
if (!m_currentSelectedPartId.isNull()) {
|
||||
m_selectedPartIds.insert(m_currentSelectedPartId);
|
||||
m_currentSelectedPartId = QUuid();
|
||||
}
|
||||
if (m_selectedPartIds.find(partId) != m_selectedPartIds.end()) {
|
||||
updateCutFaceSelectState(partId, false);
|
||||
m_selectedPartIds.erase(partId);
|
||||
} else {
|
||||
if (m_multipleSelectionEnabled || m_selectedPartIds.empty()) {
|
||||
updateCutFaceSelectState(partId, true);
|
||||
m_selectedPartIds.insert(partId);
|
||||
}
|
||||
}
|
||||
if (m_selectedPartIds.size() > 1) {
|
||||
return;
|
||||
}
|
||||
if (m_selectedPartIds.size() == 1)
|
||||
partId = *m_selectedPartIds.begin();
|
||||
else {
|
||||
partId = QUuid();
|
||||
emit currentSelectedCutFaceChanged(partId);
|
||||
}
|
||||
}
|
||||
if (!m_selectedPartIds.empty()) {
|
||||
for (const auto &id: m_selectedPartIds) {
|
||||
updateCutFaceSelectState(id, false);
|
||||
}
|
||||
m_selectedPartIds.clear();
|
||||
}
|
||||
if (m_currentSelectedPartId != partId) {
|
||||
if (!m_currentSelectedPartId.isNull()) {
|
||||
updateCutFaceSelectState(m_currentSelectedPartId, false);
|
||||
}
|
||||
m_currentSelectedPartId = partId;
|
||||
if (!m_currentSelectedPartId.isNull()) {
|
||||
updateCutFaceSelectState(m_currentSelectedPartId, true);
|
||||
}
|
||||
emit currentSelectedCutFaceChanged(m_currentSelectedPartId);
|
||||
}
|
||||
}
|
||||
|
||||
void CutFaceListWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
QModelIndex itemIndex = indexAt(event->pos());
|
||||
QTreeView::mousePressEvent(event);
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
bool multiple = QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier);
|
||||
if (itemIndex.isValid()) {
|
||||
QTreeWidgetItem *item = itemFromIndex(itemIndex);
|
||||
auto partId = QUuid(item->data(itemIndex.column(), Qt::UserRole).toString());
|
||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) {
|
||||
bool startAdd = false;
|
||||
bool stopAdd = false;
|
||||
std::vector<QUuid> waitQueue;
|
||||
for (const auto &childId: m_cutFacePartIdList) {
|
||||
if (m_shiftStartPartId == childId || partId == childId) {
|
||||
if (startAdd) {
|
||||
stopAdd = true;
|
||||
} else {
|
||||
startAdd = true;
|
||||
}
|
||||
}
|
||||
if (startAdd)
|
||||
waitQueue.push_back(childId);
|
||||
if (stopAdd)
|
||||
break;
|
||||
}
|
||||
if (stopAdd && !waitQueue.empty()) {
|
||||
if (!m_selectedPartIds.empty()) {
|
||||
for (const auto &id: m_selectedPartIds) {
|
||||
updateCutFaceSelectState(id, false);
|
||||
}
|
||||
m_selectedPartIds.clear();
|
||||
}
|
||||
if (!m_currentSelectedPartId.isNull()) {
|
||||
m_currentSelectedPartId = QUuid();
|
||||
}
|
||||
for (const auto &waitId: waitQueue) {
|
||||
selectCutFace(waitId, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
m_shiftStartPartId = partId;
|
||||
}
|
||||
selectCutFace(partId, multiple);
|
||||
return;
|
||||
}
|
||||
if (!multiple)
|
||||
selectCutFace(QUuid());
|
||||
}
|
||||
}
|
||||
|
||||
bool CutFaceListWidget::isCutFaceSelected(QUuid partId)
|
||||
{
|
||||
return (m_currentSelectedPartId == partId ||
|
||||
m_selectedPartIds.find(partId) != m_selectedPartIds.end());
|
||||
}
|
||||
|
||||
void CutFaceListWidget::showContextMenu(const QPoint &pos)
|
||||
{
|
||||
if (!m_hasContextMenu)
|
||||
return;
|
||||
}
|
||||
|
||||
void CutFaceListWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QTreeWidget::resizeEvent(event);
|
||||
if (calculateColumnCount() != columnCount())
|
||||
reload();
|
||||
}
|
||||
|
||||
int CutFaceListWidget::calculateColumnCount()
|
||||
{
|
||||
if (nullptr == parentWidget())
|
||||
return 0;
|
||||
|
||||
int columns = parentWidget()->width() / Theme::cutFacePreviewImageSize;
|
||||
if (0 == columns)
|
||||
columns = 1;
|
||||
return columns;
|
||||
}
|
||||
|
||||
void CutFaceListWidget::reload()
|
||||
{
|
||||
removeAllContent();
|
||||
|
||||
int columns = calculateColumnCount();
|
||||
if (0 == columns)
|
||||
return;
|
||||
|
||||
int columnWidth = parentWidget()->width() / columns;
|
||||
|
||||
//qDebug() << "parentWidth:" << parentWidget()->width() << "columnWidth:" << columnWidth << "columns:" << columns;
|
||||
|
||||
setColumnCount(columns);
|
||||
for (int i = 0; i < columns; i++)
|
||||
setColumnWidth(i, columnWidth);
|
||||
|
||||
decltype(m_cutFacePartIdList.size()) cutFaceIndex = 0;
|
||||
while (cutFaceIndex < m_cutFacePartIdList.size()) {
|
||||
QTreeWidgetItem *item = new QTreeWidgetItem(this);
|
||||
item->setFlags((item->flags() | Qt::ItemIsEnabled) & ~(Qt::ItemIsSelectable) & ~(Qt::ItemIsEditable));
|
||||
for (int col = 0; col < columns && cutFaceIndex < m_cutFacePartIdList.size(); col++, cutFaceIndex++) {
|
||||
const auto &partId = m_cutFacePartIdList[cutFaceIndex];
|
||||
item->setSizeHint(col, QSize(columnWidth, CutFaceWidget::preferredHeight() + 2));
|
||||
item->setData(col, Qt::UserRole, partId.toString());
|
||||
CutFaceWidget *widget = new CutFaceWidget(m_document, partId);
|
||||
setItemWidget(item, col, widget);
|
||||
widget->reload();
|
||||
widget->updateCheckedState(isCutFaceSelected(partId));
|
||||
m_itemMap[partId] = std::make_pair(item, col);
|
||||
}
|
||||
invisibleRootItem()->addChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
void CutFaceListWidget::setHasContextMenu(bool hasContextMenu)
|
||||
{
|
||||
m_hasContextMenu = hasContextMenu;
|
||||
}
|
||||
|
||||
void CutFaceListWidget::removeAllContent()
|
||||
{
|
||||
m_itemMap.clear();
|
||||
clear();
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef DUST3D_CUT_FACE_LIST_WIDGET_H
|
||||
#define DUST3D_CUT_FACE_LIST_WIDGET_H
|
||||
#include <QTreeWidget>
|
||||
#include <map>
|
||||
#include <QMouseEvent>
|
||||
#include "document.h"
|
||||
#include "cutfacewidget.h"
|
||||
|
||||
class CutFaceListWidget : public QTreeWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void currentSelectedCutFaceChanged(QUuid partId);
|
||||
public:
|
||||
CutFaceListWidget(const Document *document, QWidget *parent=nullptr);
|
||||
bool isCutFaceSelected(QUuid partId);
|
||||
void enableMultipleSelection(bool enabled);
|
||||
bool isEmpty();
|
||||
public slots:
|
||||
void reload();
|
||||
void removeAllContent();
|
||||
void showContextMenu(const QPoint &pos);
|
||||
void selectCutFace(QUuid partId, bool multiple=false);
|
||||
void setHasContextMenu(bool hasContextMenu);
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
private:
|
||||
int calculateColumnCount();
|
||||
void updateCutFaceSelectState(QUuid partId, bool selected);
|
||||
const Document *m_document = nullptr;
|
||||
std::map<QUuid, std::pair<QTreeWidgetItem *, int>> m_itemMap;
|
||||
std::set<QUuid> m_selectedPartIds;
|
||||
QUuid m_currentSelectedPartId;
|
||||
QUuid m_shiftStartPartId;
|
||||
bool m_hasContextMenu = false;
|
||||
bool m_multipleSelectionEnabled = false;
|
||||
std::vector<QUuid> m_cutFacePartIdList;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,60 @@
|
|||
#include "cutfacewidget.h"
|
||||
|
||||
CutFaceWidget::CutFaceWidget(const Document *document, QUuid partId) :
|
||||
m_partId(partId),
|
||||
m_document(document)
|
||||
{
|
||||
setObjectName("CutFaceFrame");
|
||||
|
||||
m_previewWidget = new ModelWidget(this);
|
||||
m_previewWidget->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
m_previewWidget->setFixedSize(Theme::cutFacePreviewImageSize, Theme::cutFacePreviewImageSize);
|
||||
m_previewWidget->enableMove(false);
|
||||
m_previewWidget->enableZoom(false);
|
||||
|
||||
setFixedSize(Theme::cutFacePreviewImageSize, CutFaceWidget::preferredHeight());
|
||||
|
||||
connect(document, &Document::partPreviewChanged, this, &CutFaceWidget::updatePreview);
|
||||
}
|
||||
|
||||
void CutFaceWidget::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QWidget::resizeEvent(event);
|
||||
m_previewWidget->move((width() - Theme::cutFacePreviewImageSize) / 2, 0);
|
||||
}
|
||||
|
||||
int CutFaceWidget::preferredHeight()
|
||||
{
|
||||
return Theme::cutFacePreviewImageSize;
|
||||
}
|
||||
|
||||
void CutFaceWidget::reload()
|
||||
{
|
||||
updatePreview(m_partId);
|
||||
}
|
||||
|
||||
void CutFaceWidget::updatePreview(QUuid partId)
|
||||
{
|
||||
if (partId != m_partId)
|
||||
return;
|
||||
const SkeletonPart *part = m_document->findPart(m_partId);
|
||||
if (!part) {
|
||||
qDebug() << "Part not found:" << m_partId;
|
||||
return;
|
||||
}
|
||||
MeshLoader *previewMesh = part->takePreviewMesh();
|
||||
m_previewWidget->updateMesh(previewMesh);
|
||||
}
|
||||
|
||||
void CutFaceWidget::updateCheckedState(bool checked)
|
||||
{
|
||||
if (checked)
|
||||
setStyleSheet("#CutFaceFrame {border: 1px solid " + Theme::red.name() + ";}");
|
||||
else
|
||||
setStyleSheet("#CutFaceFrame {border: 1px solid transparent;}");
|
||||
}
|
||||
|
||||
ModelWidget *CutFaceWidget::previewWidget()
|
||||
{
|
||||
return m_previewWidget;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
#ifndef DUST3D_CUT_FACE_WIDGET_H
|
||||
#define DUST3D_CUT_FACE_WIDGET_H
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QIcon>
|
||||
#include "document.h"
|
||||
#include "modelwidget.h"
|
||||
|
||||
class CutFaceWidget : public QFrame
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CutFaceWidget(const Document *document, QUuid partId);
|
||||
static int preferredHeight();
|
||||
ModelWidget *previewWidget();
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
public slots:
|
||||
void reload();
|
||||
void updatePreview(QUuid partId);
|
||||
void updateCheckedState(bool checked);
|
||||
private:
|
||||
QUuid m_partId;
|
||||
const Document *m_document = nullptr;
|
||||
ModelWidget *m_previewWidget = nullptr;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -147,6 +147,7 @@ void Document::removeEdge(QUuid edgeId)
|
|||
QUuid oldPartId = oldPart->id;
|
||||
std::vector<std::vector<QUuid>> groups;
|
||||
splitPartByEdge(&groups, edgeId);
|
||||
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
|
||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||
const auto newUuid = QUuid::createUuid();
|
||||
SkeletonPart &part = partMap[newUuid];
|
||||
|
@ -171,6 +172,7 @@ void Document::removeEdge(QUuid edgeId)
|
|||
}
|
||||
}
|
||||
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
||||
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
|
||||
emit partAdded(part.id);
|
||||
}
|
||||
for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) {
|
||||
|
@ -186,6 +188,14 @@ void Document::removeEdge(QUuid edgeId)
|
|||
emit edgeRemoved(edgeId);
|
||||
removePart(oldPartId);
|
||||
|
||||
if (!newPartNodeNumMap.empty()) {
|
||||
std::sort(newPartNodeNumMap.begin(), newPartNodeNumMap.end(), [&](
|
||||
const std::pair<QUuid, size_t> &first, const std::pair<QUuid, size_t> &second) {
|
||||
return first.second > second.second;
|
||||
});
|
||||
updateLinkedPart(oldPartId, newPartNodeNumMap[0].first);
|
||||
}
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
|
@ -207,6 +217,7 @@ void Document::removeNode(QUuid nodeId)
|
|||
QUuid oldPartId = oldPart->id;
|
||||
std::vector<std::vector<QUuid>> groups;
|
||||
splitPartByNode(&groups, nodeId);
|
||||
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
|
||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||
const auto newUuid = QUuid::createUuid();
|
||||
SkeletonPart &part = partMap[newUuid];
|
||||
|
@ -231,6 +242,7 @@ void Document::removeNode(QUuid nodeId)
|
|||
}
|
||||
}
|
||||
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
||||
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
|
||||
emit partAdded(part.id);
|
||||
}
|
||||
for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) {
|
||||
|
@ -253,6 +265,14 @@ void Document::removeNode(QUuid nodeId)
|
|||
emit nodeRemoved(nodeId);
|
||||
removePart(oldPartId);
|
||||
|
||||
if (!newPartNodeNumMap.empty()) {
|
||||
std::sort(newPartNodeNumMap.begin(), newPartNodeNumMap.end(), [&](
|
||||
const std::pair<QUuid, size_t> &first, const std::pair<QUuid, size_t> &second) {
|
||||
return first.second > second.second;
|
||||
});
|
||||
updateLinkedPart(oldPartId, newPartNodeNumMap[0].first);
|
||||
}
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
|
@ -543,12 +563,23 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId)
|
|||
emit edgeAdded(edge.id);
|
||||
|
||||
if (toPartRemoved) {
|
||||
updateLinkedPart(toPartId, fromNode->partId);
|
||||
removePart(toPartId);
|
||||
}
|
||||
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::updateLinkedPart(QUuid oldPartId, QUuid newPartId)
|
||||
{
|
||||
for (auto &partIt: partMap) {
|
||||
if (partIt.second.cutFaceLinkedId == oldPartId) {
|
||||
partIt.second.dirty = true;
|
||||
partIt.second.setCutFaceLinkedId(newPartId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Component *Document::findComponent(QUuid componentId) const
|
||||
{
|
||||
if (componentId.isNull())
|
||||
|
@ -865,10 +896,19 @@ void Document::toSnapshot(Snapshot *snapshot, const std::set<QUuid> &limitNodeId
|
|||
part["zMirrored"] = partIt.second.zMirrored ? "true" : "false";
|
||||
part["rounded"] = partIt.second.rounded ? "true" : "false";
|
||||
part["chamfered"] = partIt.second.chamfered ? "true" : "false";
|
||||
if (PartTarget::Model != partIt.second.target)
|
||||
part["target"] = PartTargetToString(partIt.second.target);
|
||||
if (partIt.second.cutRotationAdjusted())
|
||||
part["cutRotation"] = QString::number(partIt.second.cutRotation);
|
||||
if (partIt.second.cutFaceAdjusted())
|
||||
if (partIt.second.cutFaceAdjusted()) {
|
||||
if (CutFace::UserDefined == partIt.second.cutFace) {
|
||||
if (!partIt.second.cutFaceLinkedId.isNull()) {
|
||||
part["cutFace"] = partIt.second.cutFaceLinkedId.toString();
|
||||
}
|
||||
} else {
|
||||
part["cutFace"] = CutFaceToString(partIt.second.cutFace);
|
||||
}
|
||||
}
|
||||
part["dirty"] = partIt.second.dirty ? "true" : "false";
|
||||
if (partIt.second.hasColor)
|
||||
part["color"] = partIt.second.color.name();
|
||||
|
@ -1110,6 +1150,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
|||
materialIdList.push_back(newMaterialId);
|
||||
emit materialAdded(newMaterialId);
|
||||
}
|
||||
std::map<QUuid, QUuid> cutFaceLinkedIdModifyMap;
|
||||
for (const auto &partKv: snapshot.parts) {
|
||||
const auto newUuid = QUuid::createUuid();
|
||||
SkeletonPart &part = partMap[newUuid];
|
||||
|
@ -1124,12 +1165,20 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
|||
part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored"));
|
||||
part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded"));
|
||||
part.chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "chamfered"));
|
||||
part.target = PartTargetFromString(valueOfKeyInMapOrEmpty(partKv.second, "target").toUtf8().constData());
|
||||
const auto &cutRotationIt = partKv.second.find("cutRotation");
|
||||
if (cutRotationIt != partKv.second.end())
|
||||
part.setCutRotation(cutRotationIt->second.toFloat());
|
||||
const auto &cutFaceIt = partKv.second.find("cutFace");
|
||||
if (cutFaceIt != partKv.second.end())
|
||||
if (cutFaceIt != partKv.second.end()) {
|
||||
QUuid cutFaceLinkedId = QUuid(cutFaceIt->second);
|
||||
if (cutFaceLinkedId.isNull()) {
|
||||
part.setCutFace(CutFaceFromString(cutFaceIt->second.toUtf8().constData()));
|
||||
} else {
|
||||
part.setCutFaceLinkedId(cutFaceLinkedId);
|
||||
cutFaceLinkedIdModifyMap.insert({part.id, cutFaceLinkedId});
|
||||
}
|
||||
}
|
||||
if (isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "inverse")))
|
||||
inversePartIds.insert(part.id);
|
||||
const auto &colorIt = partKv.second.find("color");
|
||||
|
@ -1148,6 +1197,14 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
|||
part.materialId = oldNewIdMap[QUuid(materialIdIt->second)];
|
||||
newAddedPartIds.insert(part.id);
|
||||
}
|
||||
for (const auto &it: cutFaceLinkedIdModifyMap) {
|
||||
SkeletonPart &part = partMap[it.first];
|
||||
auto findNewLinkedId = oldNewIdMap.find(it.second);
|
||||
if (findNewLinkedId == oldNewIdMap.end())
|
||||
part.setCutFaceLinkedId(QUuid());
|
||||
else
|
||||
part.setCutFaceLinkedId(findNewLinkedId->second);
|
||||
}
|
||||
for (const auto &nodeKv: snapshot.nodes) {
|
||||
if (nodeKv.second.find("radius") == nodeKv.second.end() ||
|
||||
nodeKv.second.find("x") == nodeKv.second.end() ||
|
||||
|
@ -2220,6 +2277,21 @@ void Document::setPartChamferState(QUuid partId, bool chamfered)
|
|||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setPartTarget(QUuid partId, PartTarget target)
|
||||
{
|
||||
auto part = partMap.find(partId);
|
||||
if (part == partMap.end()) {
|
||||
qDebug() << "Part not found:" << partId;
|
||||
return;
|
||||
}
|
||||
if (part->second.target == target)
|
||||
return;
|
||||
part->second.target = target;
|
||||
part->second.dirty = true;
|
||||
emit partTargetChanged(partId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setPartCutRotation(QUuid partId, float cutRotation)
|
||||
{
|
||||
auto part = partMap.find(partId);
|
||||
|
@ -2250,6 +2322,22 @@ void Document::setPartCutFace(QUuid partId, CutFace cutFace)
|
|||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setPartCutFaceLinkedId(QUuid partId, QUuid linkedId)
|
||||
{
|
||||
auto part = partMap.find(partId);
|
||||
if (part == partMap.end()) {
|
||||
qDebug() << "Part not found:" << partId;
|
||||
return;
|
||||
}
|
||||
if (part->second.cutFace == CutFace::UserDefined &&
|
||||
part->second.cutFaceLinkedId == linkedId)
|
||||
return;
|
||||
part->second.setCutFaceLinkedId(linkedId);
|
||||
part->second.dirty = true;
|
||||
emit partCutFaceChanged(partId);
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void Document::setPartColorState(QUuid partId, bool hasColor, QColor color)
|
||||
{
|
||||
auto part = partMap.find(partId);
|
||||
|
|
|
@ -404,6 +404,7 @@ signals:
|
|||
void partCutFaceChanged(QUuid partId);
|
||||
void partMaterialIdChanged(QUuid partId);
|
||||
void partChamferStateChanged(QUuid partId);
|
||||
void partTargetChanged(QUuid partId);
|
||||
void componentCombineModeChanged(QUuid componentId);
|
||||
void cleanup();
|
||||
void originChanged();
|
||||
|
@ -558,8 +559,10 @@ public slots:
|
|||
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
||||
void setPartCutRotation(QUuid partId, float cutRotation);
|
||||
void setPartCutFace(QUuid partId, CutFace cutFace);
|
||||
void setPartCutFaceLinkedId(QUuid partId, QUuid linkedId);
|
||||
void setPartMaterialId(QUuid partId, QUuid materialId);
|
||||
void setPartChamferState(QUuid partId, bool chamfered);
|
||||
void setPartTarget(QUuid partId, PartTarget target);
|
||||
void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
|
||||
void moveComponentUp(QUuid componentId);
|
||||
void moveComponentDown(QUuid componentId);
|
||||
|
@ -632,6 +635,7 @@ private:
|
|||
void resetDirtyFlags();
|
||||
void markAllDirty();
|
||||
void removeRigResults();
|
||||
void updateLinkedPart(QUuid oldPartId, QUuid newPartId);
|
||||
private: // need initialize
|
||||
bool m_isResultMeshObsolete;
|
||||
MeshGenerator *m_meshGenerator;
|
||||
|
|
|
@ -831,6 +831,7 @@ DocumentWindow::DocumentWindow() :
|
|||
connect(partTreeWidget, &PartTreeWidget::setPartLockState, m_document, &Document::setPartLockState);
|
||||
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::hideDescendantComponents, m_document, &Document::hideDescendantComponents);
|
||||
connect(partTreeWidget, &PartTreeWidget::showDescendantComponents, m_document, &Document::showDescendantComponents);
|
||||
connect(partTreeWidget, &PartTreeWidget::lockDescendantComponents, m_document, &Document::lockDescendantComponents);
|
||||
|
|
|
@ -63,12 +63,14 @@ int MaterialWidget::preferredHeight()
|
|||
|
||||
void MaterialWidget::reload()
|
||||
{
|
||||
updatePreview();
|
||||
updateName();
|
||||
updatePreview(m_materialId);
|
||||
updateName(m_materialId);
|
||||
}
|
||||
|
||||
void MaterialWidget::updatePreview()
|
||||
void MaterialWidget::updatePreview(QUuid materialId)
|
||||
{
|
||||
if (materialId != m_materialId)
|
||||
return;
|
||||
const Material *material = m_document->findMaterial(m_materialId);
|
||||
if (!material) {
|
||||
qDebug() << "Material not found:" << m_materialId;
|
||||
|
@ -78,8 +80,10 @@ void MaterialWidget::updatePreview()
|
|||
m_previewWidget->updateMesh(previewMesh);
|
||||
}
|
||||
|
||||
void MaterialWidget::updateName()
|
||||
void MaterialWidget::updateName(QUuid materialId)
|
||||
{
|
||||
if (materialId != m_materialId)
|
||||
return;
|
||||
const Material *material = m_document->findMaterial(m_materialId);
|
||||
if (!material) {
|
||||
qDebug() << "Material not found:" << m_materialId;
|
||||
|
|
|
@ -21,8 +21,8 @@ protected:
|
|||
void resizeEvent(QResizeEvent *event) override;
|
||||
public slots:
|
||||
void reload();
|
||||
void updatePreview();
|
||||
void updateName();
|
||||
void updatePreview(QUuid materialId);
|
||||
void updateName(QUuid materialId);
|
||||
void updateCheckedState(bool checked);
|
||||
void setCornerButtonVisible(bool visible);
|
||||
private:
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "util.h"
|
||||
#include "trianglesourcenoderesolve.h"
|
||||
#include "cutface.h"
|
||||
#include "parttarget.h"
|
||||
#include "theme.h"
|
||||
|
||||
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
||||
m_snapshot(snapshot)
|
||||
|
@ -83,6 +85,20 @@ bool MeshGenerator::checkIsPartDirty(const QString &partIdString)
|
|||
return isTrueValueString(valueOfKeyInMapOrEmpty(findPart->second, "dirty"));
|
||||
}
|
||||
|
||||
bool MeshGenerator::checkIsPartDependencyDirty(const QString &partIdString)
|
||||
{
|
||||
auto findPart = m_snapshot->parts.find(partIdString);
|
||||
if (findPart == m_snapshot->parts.end()) {
|
||||
qDebug() << "Find part failed:" << partIdString;
|
||||
return false;
|
||||
}
|
||||
QString cutFaceString = valueOfKeyInMapOrEmpty(findPart->second, "cutFace");
|
||||
QUuid cutFaceLinkedPartId = QUuid(cutFaceString);
|
||||
if (cutFaceLinkedPartId.isNull())
|
||||
return false;
|
||||
return checkIsPartDirty(cutFaceString);
|
||||
}
|
||||
|
||||
bool MeshGenerator::checkIsComponentDirty(const QString &componentIdString)
|
||||
{
|
||||
bool isDirty = false;
|
||||
|
@ -108,6 +124,11 @@ bool MeshGenerator::checkIsComponentDirty(const QString &componentIdString)
|
|||
m_dirtyPartIds.insert(partId);
|
||||
isDirty = true;
|
||||
}
|
||||
if (!isDirty) {
|
||||
if (checkIsPartDependencyDirty(partId)) {
|
||||
isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &childId: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
|
||||
|
@ -149,12 +170,122 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
float deformThickness = 1.0;
|
||||
float deformWidth = 1.0;
|
||||
float cutRotation = 0.0;
|
||||
auto target = PartTargetFromString(valueOfKeyInMapOrEmpty(part, "target").toUtf8().constData());
|
||||
|
||||
CutFace cutFace = CutFaceFromString(valueOfKeyInMapOrEmpty(part, "cutFace").toUtf8().constData());
|
||||
std::vector<QVector2D> cutTemplate = CutFaceToPoints(cutFace);
|
||||
std::vector<QVector2D> cutTemplate;
|
||||
QString cutFaceString = valueOfKeyInMapOrEmpty(part, "cutFace");
|
||||
QUuid cutFaceLinkedPartId = QUuid(cutFaceString);
|
||||
if (!cutFaceLinkedPartId.isNull()) {
|
||||
auto findCutFaceLinkedPart = m_snapshot->parts.find(cutFaceString);
|
||||
if (findCutFaceLinkedPart == m_snapshot->parts.end()) {
|
||||
qDebug() << "Find cut face linked part failed:" << cutFaceString;
|
||||
} else {
|
||||
// Build node info map
|
||||
std::map<QString, std::tuple<float, float, float>> cutFaceNodeMap;
|
||||
for (const auto &nodeIdString: m_partNodeIds[cutFaceString]) {
|
||||
auto findNode = m_snapshot->nodes.find(nodeIdString);
|
||||
if (findNode == m_snapshot->nodes.end()) {
|
||||
qDebug() << "Find node failed:" << nodeIdString;
|
||||
continue;
|
||||
}
|
||||
auto &node = findNode->second;
|
||||
float radius = valueOfKeyInMapOrEmpty(node, "radius").toFloat();
|
||||
float x = (valueOfKeyInMapOrEmpty(node, "x").toFloat() - m_mainProfileMiddleX);
|
||||
float y = (m_mainProfileMiddleY - valueOfKeyInMapOrEmpty(node, "y").toFloat());
|
||||
cutFaceNodeMap.insert({nodeIdString, {radius, x, y}});
|
||||
}
|
||||
// Build edge link
|
||||
std::map<QString, std::vector<QString>> cutFaceNodeLinkMap;
|
||||
for (const auto &edgeIdString: m_partEdgeIds[cutFaceString]) {
|
||||
auto findEdge = m_snapshot->edges.find(edgeIdString);
|
||||
if (findEdge == m_snapshot->edges.end()) {
|
||||
qDebug() << "Find edge failed:" << edgeIdString;
|
||||
continue;
|
||||
}
|
||||
auto &edge = findEdge->second;
|
||||
QString fromNodeIdString = valueOfKeyInMapOrEmpty(edge, "from");
|
||||
QString toNodeIdString = valueOfKeyInMapOrEmpty(edge, "to");
|
||||
cutFaceNodeLinkMap[fromNodeIdString].push_back(toNodeIdString);
|
||||
cutFaceNodeLinkMap[toNodeIdString].push_back(fromNodeIdString);
|
||||
}
|
||||
// Find endpoint
|
||||
QString endPointNodeIdString;
|
||||
std::vector<std::pair<QString, std::tuple<float, float, float>>> endpointNodes;
|
||||
for (const auto &it: cutFaceNodeLinkMap) {
|
||||
if (1 == it.second.size()) {
|
||||
const auto &findNode = cutFaceNodeMap.find(it.first);
|
||||
if (findNode != cutFaceNodeMap.end())
|
||||
endpointNodes.push_back({it.first, findNode->second});
|
||||
}
|
||||
}
|
||||
if (!endpointNodes.empty()) {
|
||||
std::sort(endpointNodes.begin(), endpointNodes.end(), [](
|
||||
const std::pair<QString, std::tuple<float, float, float>> &first,
|
||||
const std::pair<QString, std::tuple<float, float, float>> &second) {
|
||||
const auto &firstX = std::get<1>(first.second);
|
||||
const auto &secondX = std::get<1>(second.second);
|
||||
if (firstX < secondX) {
|
||||
return true;
|
||||
} else if (firstX > secondX) {
|
||||
return false;
|
||||
} else {
|
||||
const auto &firstY = std::get<2>(first.second);
|
||||
const auto &secondY = std::get<2>(second.second);
|
||||
if (firstY > secondY) {
|
||||
return true;
|
||||
} else if (firstY < secondY) {
|
||||
return false;
|
||||
} else {
|
||||
const auto &firstRadius = std::get<0>(first.second);
|
||||
const auto &secondRadius = std::get<0>(second.second);
|
||||
if (firstRadius < secondRadius) {
|
||||
return true;
|
||||
} else if (firstRadius > secondRadius) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
endPointNodeIdString = endpointNodes[0].first;
|
||||
}
|
||||
// Loop all linked nodes
|
||||
std::vector<std::tuple<float, float, float>> cutFaceNodes;
|
||||
std::set<QString> cutFaceVisitedNodeIds;
|
||||
std::function<void (const QString &)> loopNodeLink;
|
||||
loopNodeLink = [&](const QString &fromNodeIdString) {
|
||||
auto findCutFaceNode = cutFaceNodeMap.find(fromNodeIdString);
|
||||
if (findCutFaceNode == cutFaceNodeMap.end())
|
||||
return;
|
||||
if (cutFaceVisitedNodeIds.find(fromNodeIdString) != cutFaceVisitedNodeIds.end())
|
||||
return;
|
||||
cutFaceVisitedNodeIds.insert(fromNodeIdString);
|
||||
cutFaceNodes.push_back(findCutFaceNode->second);
|
||||
auto findNeighbor = cutFaceNodeLinkMap.find(fromNodeIdString);
|
||||
if (findNeighbor == cutFaceNodeLinkMap.end())
|
||||
return;
|
||||
for (const auto &it: findNeighbor->second) {
|
||||
if (cutFaceVisitedNodeIds.find(it) == cutFaceVisitedNodeIds.end()) {
|
||||
loopNodeLink(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!endPointNodeIdString.isEmpty()) {
|
||||
loopNodeLink(endPointNodeIdString);
|
||||
}
|
||||
// Fetch points from linked nodes
|
||||
cutFacePointsFromNodes(cutTemplate, cutFaceNodes);
|
||||
}
|
||||
}
|
||||
if (cutTemplate.size() < 3) {
|
||||
CutFace cutFace = CutFaceFromString(cutFaceString.toUtf8().constData());
|
||||
cutTemplate = CutFaceToPoints(cutFace);
|
||||
}
|
||||
if (chamfered)
|
||||
nodemesh::chamferFace2D(&cutTemplate);
|
||||
//normalizeCutFacePoints(&cutTemplate);
|
||||
|
||||
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
|
||||
if (!cutRotationString.isEmpty()) {
|
||||
cutRotation = cutRotationString.toFloat();
|
||||
|
@ -327,6 +458,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
||||
}
|
||||
|
||||
bool hasMeshError = false;
|
||||
nodemesh::Combiner::Mesh *mesh = nullptr;
|
||||
|
||||
if (buildSucceed) {
|
||||
|
@ -360,17 +492,17 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
delete mesh;
|
||||
mesh = newMesh;
|
||||
} else {
|
||||
m_isSucceed = false;
|
||||
hasMeshError = true;
|
||||
qDebug() << "Xmirrored mesh generate failed";
|
||||
delete newMesh;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_isSucceed = false;
|
||||
hasMeshError = true;
|
||||
qDebug() << "Mesh built is uncombinable";
|
||||
}
|
||||
} else {
|
||||
m_isSucceed = false;
|
||||
hasMeshError = true;
|
||||
qDebug() << "Mesh build failed";
|
||||
}
|
||||
|
||||
|
@ -392,6 +524,9 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
}
|
||||
|
||||
nodemesh::trim(&partPreviewVertices, true);
|
||||
for (auto &it: partPreviewVertices) {
|
||||
it *= 2.0;
|
||||
}
|
||||
std::vector<QVector3D> partPreviewTriangleNormals;
|
||||
for (const auto &face: partCache.previewTriangles) {
|
||||
partPreviewTriangleNormals.push_back(QVector3D::normal(
|
||||
|
@ -406,6 +541,8 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
partPreviewTriangleNormals,
|
||||
&partPreviewTriangleVertexNormals);
|
||||
if (!partCache.previewTriangles.empty()) {
|
||||
if (target == PartTarget::CutFace)
|
||||
partPreviewColor = Theme::red;
|
||||
m_partPreviewMeshes[partId] = new MeshLoader(partPreviewVertices,
|
||||
partCache.previewTriangles,
|
||||
partPreviewTriangleVertexNormals,
|
||||
|
@ -425,6 +562,15 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
|||
mesh = nullptr;
|
||||
}
|
||||
|
||||
if (target != PartTarget::Model) {
|
||||
delete mesh;
|
||||
mesh = nullptr;
|
||||
}
|
||||
|
||||
if (hasMeshError && target == PartTarget::Model) {
|
||||
m_isSucceed = false;
|
||||
}
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
@ -620,12 +766,10 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
|
|||
nodemesh::Combiner::Mesh *subMesh = it.first;
|
||||
qDebug() << "Combine mode:" << CombineModeToString(childCombineMode);
|
||||
if (nullptr == subMesh) {
|
||||
m_isSucceed = false;
|
||||
qDebug() << "Child mesh is null";
|
||||
continue;
|
||||
}
|
||||
if (subMesh->isNull()) {
|
||||
m_isSucceed = false;
|
||||
qDebug() << "Child mesh is uncombinable";
|
||||
delete subMesh;
|
||||
continue;
|
||||
|
@ -906,7 +1050,6 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
|
|||
const auto &componentCache = m_cacheContext->components[componentIdString];
|
||||
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
|
||||
qDebug() << "Uncombined mesh is null";
|
||||
m_isSucceed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ private:
|
|||
void collectParts();
|
||||
bool checkIsComponentDirty(const QString &componentIdString);
|
||||
bool checkIsPartDirty(const QString &partIdString);
|
||||
bool checkIsPartDependencyDirty(const QString &partIdString);
|
||||
void checkDirtyFlags();
|
||||
nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString);
|
||||
nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
|
||||
|
|
|
@ -35,6 +35,7 @@ ModelWidget::ModelWidget(QWidget *parent) :
|
|||
setFormat(fmt);
|
||||
}
|
||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
zoom(200);
|
||||
}
|
||||
|
||||
int ModelWidget::xRot()
|
||||
|
@ -128,7 +129,7 @@ void ModelWidget::initializeGL()
|
|||
// Our camera never changes in this example.
|
||||
m_camera.setToIdentity();
|
||||
// FIXME: if change here, please also change the camera pos in PBR shader
|
||||
m_camera.translate(0, 0, -2.1);
|
||||
m_camera.translate(0, 0, -4.0);
|
||||
|
||||
// Light position is fixed.
|
||||
// FIXME: PBR render no longer use this parameter
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
#include "parttarget.h"
|
||||
|
||||
IMPL_PartTargetFromString
|
||||
IMPL_PartTargetToString
|
||||
IMPL_PartTargetToDispName
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef DUST3D_PART_TARGET_H
|
||||
#define DUST3D_PART_TARGET_H
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
|
||||
enum class PartTarget
|
||||
{
|
||||
Model = 0,
|
||||
CutFace,
|
||||
Count
|
||||
};
|
||||
PartTarget PartTargetFromString(const char *targetString);
|
||||
#define IMPL_PartTargetFromString \
|
||||
PartTarget PartTargetFromString(const char *targetString) \
|
||||
{ \
|
||||
QString target = targetString; \
|
||||
if (target == "Model") \
|
||||
return PartTarget::Model; \
|
||||
if (target == "CutFace") \
|
||||
return PartTarget::CutFace; \
|
||||
return PartTarget::Model; \
|
||||
}
|
||||
const char *PartTargetToString(PartTarget target);
|
||||
#define IMPL_PartTargetToString \
|
||||
const char *PartTargetToString(PartTarget target) \
|
||||
{ \
|
||||
switch (target) { \
|
||||
case PartTarget::Model: \
|
||||
return "Model"; \
|
||||
case PartTarget::CutFace: \
|
||||
return "CutFace"; \
|
||||
default: \
|
||||
return "Model"; \
|
||||
} \
|
||||
}
|
||||
QString PartTargetToDispName(PartTarget target);
|
||||
#define IMPL_PartTargetToDispName \
|
||||
QString PartTargetToDispName(PartTarget target) \
|
||||
{ \
|
||||
switch (target) { \
|
||||
case PartTarget::Model: \
|
||||
return QObject::tr("Model"); \
|
||||
case PartTarget::CutFace: \
|
||||
return QObject::tr("Cut Face"); \
|
||||
default: \
|
||||
return QObject::tr("Model"); \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
|
@ -7,6 +7,7 @@
|
|||
#include <QBrush>
|
||||
#include <QGuiApplication>
|
||||
#include <QComboBox>
|
||||
#include <QFormLayout>
|
||||
#include "parttreewidget.h"
|
||||
#include "partwidget.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
|
@ -276,20 +277,37 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
|
|||
combineModeSelectBox->addItem(CombineModeToDispName(mode));
|
||||
}
|
||||
combineModeSelectBox->setCurrentIndex((int)component->combineMode);
|
||||
|
||||
connect(combineModeSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
|
||||
emit setComponentCombineMode(component->id, (CombineMode)index);
|
||||
});
|
||||
|
||||
QHBoxLayout *combineModeLayout = new QHBoxLayout;
|
||||
combineModeLayout->setAlignment(Qt::AlignCenter);
|
||||
combineModeLayout->setContentsMargins(0, 0, 0, 0);
|
||||
combineModeLayout->setSpacing(0);
|
||||
combineModeLayout->addWidget(combineModeSelectBox);
|
||||
QComboBox *partTargetSelectBox = nullptr;
|
||||
if (nullptr != part && nullptr != partWidget) {
|
||||
partTargetSelectBox = new QComboBox;
|
||||
for (size_t i = 0; i < (size_t)PartTarget::Count; ++i) {
|
||||
PartTarget target = (PartTarget)i;
|
||||
partTargetSelectBox->addItem(PartTargetToDispName(target));
|
||||
}
|
||||
partTargetSelectBox->setCurrentIndex((int)part->target);
|
||||
connect(partTargetSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
|
||||
emit setPartTarget(part->id, (PartTarget)index);
|
||||
});
|
||||
}
|
||||
|
||||
//QHBoxLayout *combineModeLayout = new QHBoxLayout;
|
||||
//combineModeLayout->setAlignment(Qt::AlignCenter);
|
||||
//combineModeLayout->setContentsMargins(0, 0, 0, 0);
|
||||
//combineModeLayout->setSpacing(0);
|
||||
//combineModeLayout->addWidget(combineModeSelectBox);
|
||||
|
||||
QFormLayout *componentSettingsLayout = new QFormLayout;
|
||||
if (nullptr != partTargetSelectBox)
|
||||
componentSettingsLayout->addRow(tr("Target"), partTargetSelectBox);
|
||||
componentSettingsLayout->addRow(tr("Mode"), combineModeSelectBox);
|
||||
|
||||
QVBoxLayout *newLayout = new QVBoxLayout;
|
||||
newLayout->addLayout(layout);
|
||||
newLayout->addLayout(combineModeLayout);
|
||||
newLayout->addLayout(componentSettingsLayout);
|
||||
widget->setLayout(newLayout);
|
||||
} else {
|
||||
widget->setLayout(layout);
|
||||
|
@ -687,6 +705,11 @@ void PartTreeWidget::componentCombineModeChanged(QUuid componentId)
|
|||
updateComponentAppearance(componentId);
|
||||
}
|
||||
|
||||
void PartTreeWidget::componentTargetChanged(QUuid componentId)
|
||||
{
|
||||
updateComponentAppearance(componentId);
|
||||
}
|
||||
|
||||
void PartTreeWidget::addComponentChildrenToItem(QUuid componentId, QTreeWidgetItem *parentItem)
|
||||
{
|
||||
const Component *parentComponent = m_document->findComponent(componentId);
|
||||
|
|
|
@ -21,6 +21,7 @@ signals:
|
|||
void setComponentExpandState(QUuid componentId, bool expanded);
|
||||
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
|
||||
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
|
||||
void setPartTarget(QUuid partId, PartTarget target);
|
||||
void moveComponent(QUuid componentId, QUuid toParentId);
|
||||
void removeComponent(QUuid componentId);
|
||||
void hideOtherComponents(QUuid componentId);
|
||||
|
@ -50,6 +51,7 @@ public slots:
|
|||
void componentAdded(QUuid componentId);
|
||||
void componentExpandStateChanged(QUuid componentId);
|
||||
void componentCombineModeChanged(QUuid componentId);
|
||||
void componentTargetChanged(QUuid componentId);
|
||||
void partRemoved(QUuid partId);
|
||||
void partPreviewChanged(QUuid partid);
|
||||
void partLockStateChanged(QUuid partId);
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#include "skeletongraphicswidget.h"
|
||||
#include "shortcuts.h"
|
||||
#include "graphicscontainerwidget.h"
|
||||
#include "flowlayout.h"
|
||||
#include "cutfacelistwidget.h"
|
||||
|
||||
PartWidget::PartWidget(const Document *document, QUuid partId) :
|
||||
m_document(document),
|
||||
|
@ -73,7 +75,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
|
|||
initButton(m_colorButton);
|
||||
|
||||
m_cutRotationButton = new QPushButton;
|
||||
m_cutRotationButton->setToolTip(tr("Cut rotation"));
|
||||
m_cutRotationButton->setToolTip(tr("Cut face"));
|
||||
m_cutRotationButton->setSizePolicy(retainSizePolicy);
|
||||
initButton(m_cutRotationButton);
|
||||
|
||||
|
@ -161,6 +163,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
|
|||
connect(this, &PartWidget::setPartChamferState, m_document, &Document::setPartChamferState);
|
||||
connect(this, &PartWidget::setPartCutRotation, m_document, &Document::setPartCutRotation);
|
||||
connect(this, &PartWidget::setPartCutFace, m_document, &Document::setPartCutFace);
|
||||
connect(this, &PartWidget::setPartCutFaceLinkedId, m_document, &Document::setPartCutFaceLinkedId);
|
||||
connect(this, &PartWidget::setPartColorState, m_document, &Document::setPartColorState);
|
||||
connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId);
|
||||
connect(this, &PartWidget::checkPart, m_document, &Document::checkPart);
|
||||
|
@ -434,11 +437,16 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
|||
rotationLayout->addWidget(rotationEraser);
|
||||
rotationLayout->addWidget(rotationWidget);
|
||||
|
||||
QHBoxLayout *facesLayout = new QHBoxLayout;
|
||||
QHBoxLayout *standardFacesLayout = new QHBoxLayout;
|
||||
QPushButton *buttons[(int)CutFace::Count] = {0};
|
||||
|
||||
CutFaceListWidget *cutFaceListWidget = new CutFaceListWidget(m_document);
|
||||
size_t cutFaceTypeCount = (size_t)CutFace::Count;
|
||||
if (cutFaceListWidget->isEmpty())
|
||||
cutFaceTypeCount = (size_t)CutFace::UserDefined;
|
||||
|
||||
auto updateCutFaceButtonState = [&](size_t index) {
|
||||
for (size_t i = 0; i < (size_t)CutFace::Count; ++i) {
|
||||
for (size_t i = 0; i < (size_t)cutFaceTypeCount; ++i) {
|
||||
auto button = buttons[i];
|
||||
if (i == index) {
|
||||
button->setFlat(true);
|
||||
|
@ -448,8 +456,28 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
|||
button->setEnabled(true);
|
||||
}
|
||||
}
|
||||
if (index != (int)CutFace::UserDefined)
|
||||
cutFaceListWidget->selectCutFace(QUuid());
|
||||
};
|
||||
for (size_t i = 0; i < (size_t)CutFace::Count; ++i) {
|
||||
|
||||
cutFaceListWidget->enableMultipleSelection(false);
|
||||
cutFaceListWidget->selectCutFace(part->cutFaceLinkedId);
|
||||
connect(cutFaceListWidget, &CutFaceListWidget::currentSelectedCutFaceChanged, this, [=](QUuid partId) {
|
||||
if (partId.isNull()) {
|
||||
CutFace cutFace = CutFace::Quad;
|
||||
updateCutFaceButtonState((int)cutFace);
|
||||
emit setPartCutFace(m_partId, cutFace);
|
||||
emit groupOperationAdded();
|
||||
} else {
|
||||
updateCutFaceButtonState((int)CutFace::UserDefined);
|
||||
emit setPartCutFaceLinkedId(m_partId, partId);
|
||||
emit groupOperationAdded();
|
||||
}
|
||||
});
|
||||
if (cutFaceListWidget->isEmpty())
|
||||
cutFaceListWidget->hide();
|
||||
|
||||
for (size_t i = 0; i < (size_t)cutFaceTypeCount; ++i) {
|
||||
CutFace cutFace = (CutFace)i;
|
||||
QString iconFilename = ":/resources/" + CutFaceToString(cutFace).toLower() + ".png";
|
||||
QPixmap pixmap(iconFilename);
|
||||
|
@ -462,7 +490,7 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
|||
emit setPartCutFace(m_partId, cutFace);
|
||||
emit groupOperationAdded();
|
||||
});
|
||||
facesLayout->addWidget(button);
|
||||
standardFacesLayout->addWidget(button);
|
||||
buttons[i] = button;
|
||||
}
|
||||
updateCutFaceButtonState((size_t)part->cutFace);
|
||||
|
@ -470,7 +498,8 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
|||
QVBoxLayout *popupLayout = new QVBoxLayout;
|
||||
popupLayout->addLayout(rotationLayout);
|
||||
popupLayout->addSpacing(10);
|
||||
popupLayout->addLayout(facesLayout);
|
||||
popupLayout->addLayout(standardFacesLayout);
|
||||
popupLayout->addWidget(cutFaceListWidget);
|
||||
|
||||
popup->setLayout(popupLayout);
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ signals:
|
|||
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
||||
void setPartCutRotation(QUuid partId, float cutRotation);
|
||||
void setPartCutFace(QUuid partId, CutFace cutFace);
|
||||
void setPartCutFaceLinkedId(QUuid partId, QUuid linkedId);
|
||||
void setPartMaterialId(QUuid partId, QUuid materialId);
|
||||
void movePartUp(QUuid partId);
|
||||
void movePartDown(QUuid partId);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "theme.h"
|
||||
#include "meshloader.h"
|
||||
#include "cutface.h"
|
||||
#include "parttarget.h"
|
||||
|
||||
class SkeletonNode
|
||||
{
|
||||
|
@ -87,7 +88,9 @@ public:
|
|||
bool dirty;
|
||||
float cutRotation;
|
||||
CutFace cutFace;
|
||||
QUuid cutFaceLinkedId;
|
||||
QUuid materialId;
|
||||
PartTarget target;
|
||||
SkeletonPart(const QUuid &withId=QUuid()) :
|
||||
visible(true),
|
||||
locked(false),
|
||||
|
@ -99,11 +102,12 @@ public:
|
|||
deformWidth(1.0),
|
||||
rounded(false),
|
||||
chamfered(false),
|
||||
color(Theme::white),
|
||||
color(Qt::white),
|
||||
hasColor(false),
|
||||
dirty(true),
|
||||
cutRotation(0.0),
|
||||
cutFace(CutFace::Quad)
|
||||
cutFace(CutFace::Quad),
|
||||
target(PartTarget::Model)
|
||||
{
|
||||
id = withId.isNull() ? QUuid::createUuid() : withId;
|
||||
}
|
||||
|
@ -134,6 +138,16 @@ public:
|
|||
void setCutFace(CutFace face)
|
||||
{
|
||||
cutFace = face;
|
||||
cutFaceLinkedId = QUuid();
|
||||
}
|
||||
void setCutFaceLinkedId(const QUuid &linkedId)
|
||||
{
|
||||
if (linkedId.isNull()) {
|
||||
setCutFace(CutFace::Quad);
|
||||
return;
|
||||
}
|
||||
cutFace = CutFace::UserDefined;
|
||||
cutFaceLinkedId = linkedId;
|
||||
}
|
||||
bool deformThicknessAdjusted() const
|
||||
{
|
||||
|
@ -183,9 +197,11 @@ public:
|
|||
hasColor = other.hasColor;
|
||||
cutRotation = other.cutRotation;
|
||||
cutFace = other.cutFace;
|
||||
cutFaceLinkedId = other.cutFaceLinkedId;
|
||||
componentId = other.componentId;
|
||||
dirty = other.dirty;
|
||||
materialId = other.materialId;
|
||||
target = other.target;
|
||||
}
|
||||
void updatePreviewMesh(MeshLoader *previewMesh)
|
||||
{
|
||||
|
|
|
@ -39,6 +39,7 @@ int Theme::miniIconFontSize = 0;
|
|||
int Theme::miniIconSize = 0;
|
||||
int Theme::partPreviewImageSize = 0;
|
||||
int Theme::materialPreviewImageSize = 0;
|
||||
int Theme::cutFacePreviewImageSize = 0;
|
||||
int Theme::posePreviewImageSize = 0;
|
||||
int Theme::motionPreviewImageSize = 0;
|
||||
int Theme::sidebarPreferredWidth = 0;
|
||||
|
@ -54,6 +55,7 @@ void Theme::initAwsomeBaseSizes()
|
|||
Theme::miniIconSize = (int)(Theme::miniIconFontSize * 1.67);
|
||||
Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
||||
Theme::materialPreviewImageSize = 75;
|
||||
Theme::cutFacePreviewImageSize = 75;
|
||||
Theme::posePreviewImageSize = 75;
|
||||
Theme::motionPreviewImageSize = 75;
|
||||
Theme::sidebarPreferredWidth = 200;
|
||||
|
|
|
@ -35,6 +35,7 @@ public:
|
|||
static int toolIconFontSize;
|
||||
static int toolIconSize;
|
||||
static int materialPreviewImageSize;
|
||||
static int cutFacePreviewImageSize;
|
||||
static int posePreviewImageSize;
|
||||
static int partPreviewImageSize;
|
||||
static int motionPreviewImageSize;
|
||||
|
|
|
@ -616,7 +616,6 @@ void Builder::makeCut(const QVector3D &position,
|
|||
const QVector3D &traverseDirection,
|
||||
std::vector<QVector3D> &resultCut)
|
||||
{
|
||||
baseNormal = revisedBaseNormalAcordingToCutNormal(baseNormal, cutNormal);
|
||||
auto finalCutTemplate = cutTemplate;
|
||||
auto finalCutNormal = cutNormal;
|
||||
float degree = 0;
|
||||
|
@ -624,14 +623,9 @@ void Builder::makeCut(const QVector3D &position,
|
|||
degree = m_cutRotation * 180;
|
||||
}
|
||||
if (QVector3D::dotProduct(cutNormal, traverseDirection) <= 0) {
|
||||
baseNormal = -baseNormal;
|
||||
finalCutNormal = -finalCutNormal;
|
||||
std::reverse(finalCutTemplate.begin(), finalCutTemplate.end());
|
||||
degree = ((int)degree + 180) % 360;
|
||||
//for (auto &it: finalCutTemplate) {
|
||||
// it.setX(-it.x());
|
||||
// it.setY(-it.y());
|
||||
//}
|
||||
std::rotate(finalCutTemplate.begin(), finalCutTemplate.begin() + finalCutTemplate.size() - 1, finalCutTemplate.end());
|
||||
}
|
||||
QVector3D u = QVector3D::crossProduct(finalCutNormal, baseNormal).normalized();
|
||||
QVector3D v = QVector3D::crossProduct(u, finalCutNormal).normalized();
|
||||
|
|
Loading…
Reference in New Issue