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
|
SOURCES += src/cutface.cpp
|
||||||
HEADERS += src/cutface.h
|
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
|
SOURCES += src/remoteioserver.cpp
|
||||||
HEADERS += src/remoteioserver.h
|
HEADERS += src/remoteioserver.h
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ void main()
|
||||||
vert = (modelMatrix * vertex).xyz;
|
vert = (modelMatrix * vertex).xyz;
|
||||||
vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz);
|
vertNormal = normalize((modelMatrix * vec4(normal, 1.0)).xyz);
|
||||||
vertColor = color;
|
vertColor = color;
|
||||||
cameraPos = vec3(0, 0, -2.1);
|
cameraPos = vec3(0, 0, -4.0);
|
||||||
|
|
||||||
firstLightPos = vec3(5.0, 5.0, 5.0);
|
firstLightPos = vec3(5.0, 5.0, 5.0);
|
||||||
secondLightPos = vec3(-5.0, 5.0, 5.0);
|
secondLightPos = vec3(-5.0, 5.0, 5.0);
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
#include <QVector2D>
|
||||||
|
#include <QVector3D>
|
||||||
#include "cutface.h"
|
#include "cutface.h"
|
||||||
|
|
||||||
IMPL_CutFaceFromString
|
IMPL_CutFaceFromString
|
||||||
|
@ -38,3 +40,45 @@ void normalizeCutFacePoints(std::vector<QVector2D> *points)
|
||||||
position.setY((position.y() - yMiddle) * 2 / longSize);
|
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,
|
Pentagon,
|
||||||
Hexagon,
|
Hexagon,
|
||||||
Triangle,
|
Triangle,
|
||||||
//UserDefined,
|
UserDefined,
|
||||||
Count
|
Count
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ CutFace CutFaceFromString(const char *faceString) \
|
||||||
return CutFace::Hexagon; \
|
return CutFace::Hexagon; \
|
||||||
if (face == "Triangle") \
|
if (face == "Triangle") \
|
||||||
return CutFace::Triangle; \
|
return CutFace::Triangle; \
|
||||||
|
if (face == "UserDefined") \
|
||||||
|
return CutFace::UserDefined; \
|
||||||
return CutFace::Quad; \
|
return CutFace::Quad; \
|
||||||
}
|
}
|
||||||
QString CutFaceToString(CutFace cutFace);
|
QString CutFaceToString(CutFace cutFace);
|
||||||
|
@ -43,6 +45,8 @@ QString CutFaceToString(CutFace cutFace) \
|
||||||
return "Hexagon"; \
|
return "Hexagon"; \
|
||||||
case CutFace::Triangle: \
|
case CutFace::Triangle: \
|
||||||
return "Triangle"; \
|
return "Triangle"; \
|
||||||
|
case CutFace::UserDefined: \
|
||||||
|
return "UserDefined"; \
|
||||||
default: \
|
default: \
|
||||||
return ""; \
|
return ""; \
|
||||||
} \
|
} \
|
||||||
|
@ -93,5 +97,6 @@ std::vector<QVector2D> CutFaceToPoints(CutFace cutFace) \
|
||||||
}
|
}
|
||||||
|
|
||||||
void normalizeCutFacePoints(std::vector<QVector2D> *points);
|
void normalizeCutFacePoints(std::vector<QVector2D> *points);
|
||||||
|
void cutFacePointsFromNodes(std::vector<QVector2D> &points, const std::vector<std::tuple<float, float, float>> &nodes);
|
||||||
|
|
||||||
#endif
|
#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;
|
QUuid oldPartId = oldPart->id;
|
||||||
std::vector<std::vector<QUuid>> groups;
|
std::vector<std::vector<QUuid>> groups;
|
||||||
splitPartByEdge(&groups, edgeId);
|
splitPartByEdge(&groups, edgeId);
|
||||||
|
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
|
||||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||||
const auto newUuid = QUuid::createUuid();
|
const auto newUuid = QUuid::createUuid();
|
||||||
SkeletonPart &part = partMap[newUuid];
|
SkeletonPart &part = partMap[newUuid];
|
||||||
|
@ -171,6 +172,7 @@ void Document::removeEdge(QUuid edgeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
||||||
|
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
|
||||||
emit partAdded(part.id);
|
emit partAdded(part.id);
|
||||||
}
|
}
|
||||||
for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) {
|
for (auto nodeIdIt = edge->nodeIds.begin(); nodeIdIt != edge->nodeIds.end(); nodeIdIt++) {
|
||||||
|
@ -186,6 +188,14 @@ void Document::removeEdge(QUuid edgeId)
|
||||||
emit edgeRemoved(edgeId);
|
emit edgeRemoved(edgeId);
|
||||||
removePart(oldPartId);
|
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();
|
emit skeletonChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,6 +217,7 @@ void Document::removeNode(QUuid nodeId)
|
||||||
QUuid oldPartId = oldPart->id;
|
QUuid oldPartId = oldPart->id;
|
||||||
std::vector<std::vector<QUuid>> groups;
|
std::vector<std::vector<QUuid>> groups;
|
||||||
splitPartByNode(&groups, nodeId);
|
splitPartByNode(&groups, nodeId);
|
||||||
|
std::vector<std::pair<QUuid, size_t>> newPartNodeNumMap;
|
||||||
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
for (auto groupIt = groups.begin(); groupIt != groups.end(); groupIt++) {
|
||||||
const auto newUuid = QUuid::createUuid();
|
const auto newUuid = QUuid::createUuid();
|
||||||
SkeletonPart &part = partMap[newUuid];
|
SkeletonPart &part = partMap[newUuid];
|
||||||
|
@ -231,6 +242,7 @@ void Document::removeNode(QUuid nodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
addPartToComponent(part.id, findComponentParentId(part.componentId));
|
||||||
|
newPartNodeNumMap.push_back({part.id, part.nodeIds.size()});
|
||||||
emit partAdded(part.id);
|
emit partAdded(part.id);
|
||||||
}
|
}
|
||||||
for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) {
|
for (auto edgeIdIt = node->edgeIds.begin(); edgeIdIt != node->edgeIds.end(); edgeIdIt++) {
|
||||||
|
@ -253,6 +265,14 @@ void Document::removeNode(QUuid nodeId)
|
||||||
emit nodeRemoved(nodeId);
|
emit nodeRemoved(nodeId);
|
||||||
removePart(oldPartId);
|
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();
|
emit skeletonChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -543,12 +563,23 @@ void Document::addEdge(QUuid fromNodeId, QUuid toNodeId)
|
||||||
emit edgeAdded(edge.id);
|
emit edgeAdded(edge.id);
|
||||||
|
|
||||||
if (toPartRemoved) {
|
if (toPartRemoved) {
|
||||||
|
updateLinkedPart(toPartId, fromNode->partId);
|
||||||
removePart(toPartId);
|
removePart(toPartId);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit skeletonChanged();
|
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
|
const Component *Document::findComponent(QUuid componentId) const
|
||||||
{
|
{
|
||||||
if (componentId.isNull())
|
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["zMirrored"] = partIt.second.zMirrored ? "true" : "false";
|
||||||
part["rounded"] = partIt.second.rounded ? "true" : "false";
|
part["rounded"] = partIt.second.rounded ? "true" : "false";
|
||||||
part["chamfered"] = partIt.second.chamfered ? "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())
|
if (partIt.second.cutRotationAdjusted())
|
||||||
part["cutRotation"] = QString::number(partIt.second.cutRotation);
|
part["cutRotation"] = QString::number(partIt.second.cutRotation);
|
||||||
if (partIt.second.cutFaceAdjusted())
|
if (partIt.second.cutFaceAdjusted()) {
|
||||||
part["cutFace"] = CutFaceToString(partIt.second.cutFace);
|
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";
|
part["dirty"] = partIt.second.dirty ? "true" : "false";
|
||||||
if (partIt.second.hasColor)
|
if (partIt.second.hasColor)
|
||||||
part["color"] = partIt.second.color.name();
|
part["color"] = partIt.second.color.name();
|
||||||
|
@ -1110,6 +1150,7 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
||||||
materialIdList.push_back(newMaterialId);
|
materialIdList.push_back(newMaterialId);
|
||||||
emit materialAdded(newMaterialId);
|
emit materialAdded(newMaterialId);
|
||||||
}
|
}
|
||||||
|
std::map<QUuid, QUuid> cutFaceLinkedIdModifyMap;
|
||||||
for (const auto &partKv: snapshot.parts) {
|
for (const auto &partKv: snapshot.parts) {
|
||||||
const auto newUuid = QUuid::createUuid();
|
const auto newUuid = QUuid::createUuid();
|
||||||
SkeletonPart &part = partMap[newUuid];
|
SkeletonPart &part = partMap[newUuid];
|
||||||
|
@ -1124,12 +1165,20 @@ void Document::addFromSnapshot(const Snapshot &snapshot, bool fromPaste)
|
||||||
part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored"));
|
part.zMirrored = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "zMirrored"));
|
||||||
part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded"));
|
part.rounded = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "rounded"));
|
||||||
part.chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "chamfered"));
|
part.chamfered = isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "chamfered"));
|
||||||
|
part.target = PartTargetFromString(valueOfKeyInMapOrEmpty(partKv.second, "target").toUtf8().constData());
|
||||||
const auto &cutRotationIt = partKv.second.find("cutRotation");
|
const auto &cutRotationIt = partKv.second.find("cutRotation");
|
||||||
if (cutRotationIt != partKv.second.end())
|
if (cutRotationIt != partKv.second.end())
|
||||||
part.setCutRotation(cutRotationIt->second.toFloat());
|
part.setCutRotation(cutRotationIt->second.toFloat());
|
||||||
const auto &cutFaceIt = partKv.second.find("cutFace");
|
const auto &cutFaceIt = partKv.second.find("cutFace");
|
||||||
if (cutFaceIt != partKv.second.end())
|
if (cutFaceIt != partKv.second.end()) {
|
||||||
part.setCutFace(CutFaceFromString(cutFaceIt->second.toUtf8().constData()));
|
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")))
|
if (isTrueValueString(valueOfKeyInMapOrEmpty(partKv.second, "inverse")))
|
||||||
inversePartIds.insert(part.id);
|
inversePartIds.insert(part.id);
|
||||||
const auto &colorIt = partKv.second.find("color");
|
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)];
|
part.materialId = oldNewIdMap[QUuid(materialIdIt->second)];
|
||||||
newAddedPartIds.insert(part.id);
|
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) {
|
for (const auto &nodeKv: snapshot.nodes) {
|
||||||
if (nodeKv.second.find("radius") == nodeKv.second.end() ||
|
if (nodeKv.second.find("radius") == nodeKv.second.end() ||
|
||||||
nodeKv.second.find("x") == nodeKv.second.end() ||
|
nodeKv.second.find("x") == nodeKv.second.end() ||
|
||||||
|
@ -2220,6 +2277,21 @@ void Document::setPartChamferState(QUuid partId, bool chamfered)
|
||||||
emit skeletonChanged();
|
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)
|
void Document::setPartCutRotation(QUuid partId, float cutRotation)
|
||||||
{
|
{
|
||||||
auto part = partMap.find(partId);
|
auto part = partMap.find(partId);
|
||||||
|
@ -2250,6 +2322,22 @@ void Document::setPartCutFace(QUuid partId, CutFace cutFace)
|
||||||
emit skeletonChanged();
|
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)
|
void Document::setPartColorState(QUuid partId, bool hasColor, QColor color)
|
||||||
{
|
{
|
||||||
auto part = partMap.find(partId);
|
auto part = partMap.find(partId);
|
||||||
|
|
|
@ -404,6 +404,7 @@ signals:
|
||||||
void partCutFaceChanged(QUuid partId);
|
void partCutFaceChanged(QUuid partId);
|
||||||
void partMaterialIdChanged(QUuid partId);
|
void partMaterialIdChanged(QUuid partId);
|
||||||
void partChamferStateChanged(QUuid partId);
|
void partChamferStateChanged(QUuid partId);
|
||||||
|
void partTargetChanged(QUuid partId);
|
||||||
void componentCombineModeChanged(QUuid componentId);
|
void componentCombineModeChanged(QUuid componentId);
|
||||||
void cleanup();
|
void cleanup();
|
||||||
void originChanged();
|
void originChanged();
|
||||||
|
@ -558,8 +559,10 @@ public slots:
|
||||||
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
||||||
void setPartCutRotation(QUuid partId, float cutRotation);
|
void setPartCutRotation(QUuid partId, float cutRotation);
|
||||||
void setPartCutFace(QUuid partId, CutFace cutFace);
|
void setPartCutFace(QUuid partId, CutFace cutFace);
|
||||||
|
void setPartCutFaceLinkedId(QUuid partId, QUuid linkedId);
|
||||||
void setPartMaterialId(QUuid partId, QUuid materialId);
|
void setPartMaterialId(QUuid partId, QUuid materialId);
|
||||||
void setPartChamferState(QUuid partId, bool chamfered);
|
void setPartChamferState(QUuid partId, bool chamfered);
|
||||||
|
void setPartTarget(QUuid partId, PartTarget target);
|
||||||
void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
|
void setComponentCombineMode(QUuid componentId, CombineMode combineMode);
|
||||||
void moveComponentUp(QUuid componentId);
|
void moveComponentUp(QUuid componentId);
|
||||||
void moveComponentDown(QUuid componentId);
|
void moveComponentDown(QUuid componentId);
|
||||||
|
@ -632,6 +635,7 @@ private:
|
||||||
void resetDirtyFlags();
|
void resetDirtyFlags();
|
||||||
void markAllDirty();
|
void markAllDirty();
|
||||||
void removeRigResults();
|
void removeRigResults();
|
||||||
|
void updateLinkedPart(QUuid oldPartId, QUuid newPartId);
|
||||||
private: // need initialize
|
private: // need initialize
|
||||||
bool m_isResultMeshObsolete;
|
bool m_isResultMeshObsolete;
|
||||||
MeshGenerator *m_meshGenerator;
|
MeshGenerator *m_meshGenerator;
|
||||||
|
|
|
@ -831,6 +831,7 @@ DocumentWindow::DocumentWindow() :
|
||||||
connect(partTreeWidget, &PartTreeWidget::setPartLockState, m_document, &Document::setPartLockState);
|
connect(partTreeWidget, &PartTreeWidget::setPartLockState, m_document, &Document::setPartLockState);
|
||||||
connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState);
|
connect(partTreeWidget, &PartTreeWidget::setPartVisibleState, m_document, &Document::setPartVisibleState);
|
||||||
connect(partTreeWidget, &PartTreeWidget::setComponentCombineMode, m_document, &Document::setComponentCombineMode);
|
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::hideDescendantComponents, m_document, &Document::hideDescendantComponents);
|
||||||
connect(partTreeWidget, &PartTreeWidget::showDescendantComponents, m_document, &Document::showDescendantComponents);
|
connect(partTreeWidget, &PartTreeWidget::showDescendantComponents, m_document, &Document::showDescendantComponents);
|
||||||
connect(partTreeWidget, &PartTreeWidget::lockDescendantComponents, m_document, &Document::lockDescendantComponents);
|
connect(partTreeWidget, &PartTreeWidget::lockDescendantComponents, m_document, &Document::lockDescendantComponents);
|
||||||
|
|
|
@ -63,12 +63,14 @@ int MaterialWidget::preferredHeight()
|
||||||
|
|
||||||
void MaterialWidget::reload()
|
void MaterialWidget::reload()
|
||||||
{
|
{
|
||||||
updatePreview();
|
updatePreview(m_materialId);
|
||||||
updateName();
|
updateName(m_materialId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaterialWidget::updatePreview()
|
void MaterialWidget::updatePreview(QUuid materialId)
|
||||||
{
|
{
|
||||||
|
if (materialId != m_materialId)
|
||||||
|
return;
|
||||||
const Material *material = m_document->findMaterial(m_materialId);
|
const Material *material = m_document->findMaterial(m_materialId);
|
||||||
if (!material) {
|
if (!material) {
|
||||||
qDebug() << "Material not found:" << m_materialId;
|
qDebug() << "Material not found:" << m_materialId;
|
||||||
|
@ -78,8 +80,10 @@ void MaterialWidget::updatePreview()
|
||||||
m_previewWidget->updateMesh(previewMesh);
|
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);
|
const Material *material = m_document->findMaterial(m_materialId);
|
||||||
if (!material) {
|
if (!material) {
|
||||||
qDebug() << "Material not found:" << m_materialId;
|
qDebug() << "Material not found:" << m_materialId;
|
||||||
|
|
|
@ -21,8 +21,8 @@ protected:
|
||||||
void resizeEvent(QResizeEvent *event) override;
|
void resizeEvent(QResizeEvent *event) override;
|
||||||
public slots:
|
public slots:
|
||||||
void reload();
|
void reload();
|
||||||
void updatePreview();
|
void updatePreview(QUuid materialId);
|
||||||
void updateName();
|
void updateName(QUuid materialId);
|
||||||
void updateCheckedState(bool checked);
|
void updateCheckedState(bool checked);
|
||||||
void setCornerButtonVisible(bool visible);
|
void setCornerButtonVisible(bool visible);
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "trianglesourcenoderesolve.h"
|
#include "trianglesourcenoderesolve.h"
|
||||||
#include "cutface.h"
|
#include "cutface.h"
|
||||||
|
#include "parttarget.h"
|
||||||
|
#include "theme.h"
|
||||||
|
|
||||||
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
MeshGenerator::MeshGenerator(Snapshot *snapshot) :
|
||||||
m_snapshot(snapshot)
|
m_snapshot(snapshot)
|
||||||
|
@ -83,6 +85,20 @@ bool MeshGenerator::checkIsPartDirty(const QString &partIdString)
|
||||||
return isTrueValueString(valueOfKeyInMapOrEmpty(findPart->second, "dirty"));
|
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 MeshGenerator::checkIsComponentDirty(const QString &componentIdString)
|
||||||
{
|
{
|
||||||
bool isDirty = false;
|
bool isDirty = false;
|
||||||
|
@ -108,6 +124,11 @@ bool MeshGenerator::checkIsComponentDirty(const QString &componentIdString)
|
||||||
m_dirtyPartIds.insert(partId);
|
m_dirtyPartIds.insert(partId);
|
||||||
isDirty = true;
|
isDirty = true;
|
||||||
}
|
}
|
||||||
|
if (!isDirty) {
|
||||||
|
if (checkIsPartDependencyDirty(partId)) {
|
||||||
|
isDirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &childId: valueOfKeyInMapOrEmpty(*component, "children").split(",")) {
|
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 deformThickness = 1.0;
|
||||||
float deformWidth = 1.0;
|
float deformWidth = 1.0;
|
||||||
float cutRotation = 0.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;
|
||||||
std::vector<QVector2D> cutTemplate = CutFaceToPoints(cutFace);
|
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)
|
if (chamfered)
|
||||||
nodemesh::chamferFace2D(&cutTemplate);
|
nodemesh::chamferFace2D(&cutTemplate);
|
||||||
//normalizeCutFacePoints(&cutTemplate);
|
|
||||||
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
|
QString cutRotationString = valueOfKeyInMapOrEmpty(part, "cutRotation");
|
||||||
if (!cutRotationString.isEmpty()) {
|
if (!cutRotationString.isEmpty()) {
|
||||||
cutRotation = cutRotationString.toFloat();
|
cutRotation = cutRotationString.toFloat();
|
||||||
|
@ -327,6 +458,7 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
||||||
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
partCache.outcomeNodeVertices.push_back({position, {partIdString, nodeIdString}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasMeshError = false;
|
||||||
nodemesh::Combiner::Mesh *mesh = nullptr;
|
nodemesh::Combiner::Mesh *mesh = nullptr;
|
||||||
|
|
||||||
if (buildSucceed) {
|
if (buildSucceed) {
|
||||||
|
@ -360,17 +492,17 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
||||||
delete mesh;
|
delete mesh;
|
||||||
mesh = newMesh;
|
mesh = newMesh;
|
||||||
} else {
|
} else {
|
||||||
m_isSucceed = false;
|
hasMeshError = true;
|
||||||
qDebug() << "Xmirrored mesh generate failed";
|
qDebug() << "Xmirrored mesh generate failed";
|
||||||
delete newMesh;
|
delete newMesh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_isSucceed = false;
|
hasMeshError = true;
|
||||||
qDebug() << "Mesh built is uncombinable";
|
qDebug() << "Mesh built is uncombinable";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m_isSucceed = false;
|
hasMeshError = true;
|
||||||
qDebug() << "Mesh build failed";
|
qDebug() << "Mesh build failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,6 +524,9 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
||||||
}
|
}
|
||||||
|
|
||||||
nodemesh::trim(&partPreviewVertices, true);
|
nodemesh::trim(&partPreviewVertices, true);
|
||||||
|
for (auto &it: partPreviewVertices) {
|
||||||
|
it *= 2.0;
|
||||||
|
}
|
||||||
std::vector<QVector3D> partPreviewTriangleNormals;
|
std::vector<QVector3D> partPreviewTriangleNormals;
|
||||||
for (const auto &face: partCache.previewTriangles) {
|
for (const auto &face: partCache.previewTriangles) {
|
||||||
partPreviewTriangleNormals.push_back(QVector3D::normal(
|
partPreviewTriangleNormals.push_back(QVector3D::normal(
|
||||||
|
@ -406,6 +541,8 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
||||||
partPreviewTriangleNormals,
|
partPreviewTriangleNormals,
|
||||||
&partPreviewTriangleVertexNormals);
|
&partPreviewTriangleVertexNormals);
|
||||||
if (!partCache.previewTriangles.empty()) {
|
if (!partCache.previewTriangles.empty()) {
|
||||||
|
if (target == PartTarget::CutFace)
|
||||||
|
partPreviewColor = Theme::red;
|
||||||
m_partPreviewMeshes[partId] = new MeshLoader(partPreviewVertices,
|
m_partPreviewMeshes[partId] = new MeshLoader(partPreviewVertices,
|
||||||
partCache.previewTriangles,
|
partCache.previewTriangles,
|
||||||
partPreviewTriangleVertexNormals,
|
partPreviewTriangleVertexNormals,
|
||||||
|
@ -425,6 +562,15 @@ nodemesh::Combiner::Mesh *MeshGenerator::combinePartMesh(const QString &partIdSt
|
||||||
mesh = nullptr;
|
mesh = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (target != PartTarget::Model) {
|
||||||
|
delete mesh;
|
||||||
|
mesh = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMeshError && target == PartTarget::Model) {
|
||||||
|
m_isSucceed = false;
|
||||||
|
}
|
||||||
|
|
||||||
return mesh;
|
return mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -620,12 +766,10 @@ nodemesh::Combiner::Mesh *MeshGenerator::combineMultipleMeshes(const std::vector
|
||||||
nodemesh::Combiner::Mesh *subMesh = it.first;
|
nodemesh::Combiner::Mesh *subMesh = it.first;
|
||||||
qDebug() << "Combine mode:" << CombineModeToString(childCombineMode);
|
qDebug() << "Combine mode:" << CombineModeToString(childCombineMode);
|
||||||
if (nullptr == subMesh) {
|
if (nullptr == subMesh) {
|
||||||
m_isSucceed = false;
|
|
||||||
qDebug() << "Child mesh is null";
|
qDebug() << "Child mesh is null";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (subMesh->isNull()) {
|
if (subMesh->isNull()) {
|
||||||
m_isSucceed = false;
|
|
||||||
qDebug() << "Child mesh is uncombinable";
|
qDebug() << "Child mesh is uncombinable";
|
||||||
delete subMesh;
|
delete subMesh;
|
||||||
continue;
|
continue;
|
||||||
|
@ -906,7 +1050,6 @@ void MeshGenerator::collectUncombinedComponent(const QString &componentIdString)
|
||||||
const auto &componentCache = m_cacheContext->components[componentIdString];
|
const auto &componentCache = m_cacheContext->components[componentIdString];
|
||||||
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
|
if (nullptr == componentCache.mesh || componentCache.mesh->isNull()) {
|
||||||
qDebug() << "Uncombined mesh is null";
|
qDebug() << "Uncombined mesh is null";
|
||||||
m_isSucceed = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ private:
|
||||||
void collectParts();
|
void collectParts();
|
||||||
bool checkIsComponentDirty(const QString &componentIdString);
|
bool checkIsComponentDirty(const QString &componentIdString);
|
||||||
bool checkIsPartDirty(const QString &partIdString);
|
bool checkIsPartDirty(const QString &partIdString);
|
||||||
|
bool checkIsPartDependencyDirty(const QString &partIdString);
|
||||||
void checkDirtyFlags();
|
void checkDirtyFlags();
|
||||||
nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString);
|
nodemesh::Combiner::Mesh *combinePartMesh(const QString &partIdString);
|
||||||
nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
|
nodemesh::Combiner::Mesh *combineComponentMesh(const QString &componentIdString, CombineMode *combineMode);
|
||||||
|
|
|
@ -35,6 +35,7 @@ ModelWidget::ModelWidget(QWidget *parent) :
|
||||||
setFormat(fmt);
|
setFormat(fmt);
|
||||||
}
|
}
|
||||||
setContextMenuPolicy(Qt::CustomContextMenu);
|
setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
zoom(200);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ModelWidget::xRot()
|
int ModelWidget::xRot()
|
||||||
|
@ -128,7 +129,7 @@ void ModelWidget::initializeGL()
|
||||||
// Our camera never changes in this example.
|
// Our camera never changes in this example.
|
||||||
m_camera.setToIdentity();
|
m_camera.setToIdentity();
|
||||||
// FIXME: if change here, please also change the camera pos in PBR shader
|
// 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.
|
// Light position is fixed.
|
||||||
// FIXME: PBR render no longer use this parameter
|
// 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 <QBrush>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
|
#include <QFormLayout>
|
||||||
#include "parttreewidget.h"
|
#include "parttreewidget.h"
|
||||||
#include "partwidget.h"
|
#include "partwidget.h"
|
||||||
#include "skeletongraphicswidget.h"
|
#include "skeletongraphicswidget.h"
|
||||||
|
@ -276,20 +277,37 @@ void PartTreeWidget::showContextMenu(const QPoint &pos)
|
||||||
combineModeSelectBox->addItem(CombineModeToDispName(mode));
|
combineModeSelectBox->addItem(CombineModeToDispName(mode));
|
||||||
}
|
}
|
||||||
combineModeSelectBox->setCurrentIndex((int)component->combineMode);
|
combineModeSelectBox->setCurrentIndex((int)component->combineMode);
|
||||||
|
|
||||||
connect(combineModeSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
|
connect(combineModeSelectBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [=](int index) {
|
||||||
emit setComponentCombineMode(component->id, (CombineMode)index);
|
emit setComponentCombineMode(component->id, (CombineMode)index);
|
||||||
});
|
});
|
||||||
|
|
||||||
QHBoxLayout *combineModeLayout = new QHBoxLayout;
|
QComboBox *partTargetSelectBox = nullptr;
|
||||||
combineModeLayout->setAlignment(Qt::AlignCenter);
|
if (nullptr != part && nullptr != partWidget) {
|
||||||
combineModeLayout->setContentsMargins(0, 0, 0, 0);
|
partTargetSelectBox = new QComboBox;
|
||||||
combineModeLayout->setSpacing(0);
|
for (size_t i = 0; i < (size_t)PartTarget::Count; ++i) {
|
||||||
combineModeLayout->addWidget(combineModeSelectBox);
|
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;
|
QVBoxLayout *newLayout = new QVBoxLayout;
|
||||||
newLayout->addLayout(layout);
|
newLayout->addLayout(layout);
|
||||||
newLayout->addLayout(combineModeLayout);
|
newLayout->addLayout(componentSettingsLayout);
|
||||||
widget->setLayout(newLayout);
|
widget->setLayout(newLayout);
|
||||||
} else {
|
} else {
|
||||||
widget->setLayout(layout);
|
widget->setLayout(layout);
|
||||||
|
@ -687,6 +705,11 @@ void PartTreeWidget::componentCombineModeChanged(QUuid componentId)
|
||||||
updateComponentAppearance(componentId);
|
updateComponentAppearance(componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PartTreeWidget::componentTargetChanged(QUuid componentId)
|
||||||
|
{
|
||||||
|
updateComponentAppearance(componentId);
|
||||||
|
}
|
||||||
|
|
||||||
void PartTreeWidget::addComponentChildrenToItem(QUuid componentId, QTreeWidgetItem *parentItem)
|
void PartTreeWidget::addComponentChildrenToItem(QUuid componentId, QTreeWidgetItem *parentItem)
|
||||||
{
|
{
|
||||||
const Component *parentComponent = m_document->findComponent(componentId);
|
const Component *parentComponent = m_document->findComponent(componentId);
|
||||||
|
|
|
@ -21,6 +21,7 @@ signals:
|
||||||
void setComponentExpandState(QUuid componentId, bool expanded);
|
void setComponentExpandState(QUuid componentId, bool expanded);
|
||||||
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
|
void setComponentSmoothAll(QUuid componentId, float toSmoothAll);
|
||||||
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
|
void setComponentSmoothSeam(QUuid componentId, float toSmoothSeam);
|
||||||
|
void setPartTarget(QUuid partId, PartTarget target);
|
||||||
void moveComponent(QUuid componentId, QUuid toParentId);
|
void moveComponent(QUuid componentId, QUuid toParentId);
|
||||||
void removeComponent(QUuid componentId);
|
void removeComponent(QUuid componentId);
|
||||||
void hideOtherComponents(QUuid componentId);
|
void hideOtherComponents(QUuid componentId);
|
||||||
|
@ -50,6 +51,7 @@ public slots:
|
||||||
void componentAdded(QUuid componentId);
|
void componentAdded(QUuid componentId);
|
||||||
void componentExpandStateChanged(QUuid componentId);
|
void componentExpandStateChanged(QUuid componentId);
|
||||||
void componentCombineModeChanged(QUuid componentId);
|
void componentCombineModeChanged(QUuid componentId);
|
||||||
|
void componentTargetChanged(QUuid componentId);
|
||||||
void partRemoved(QUuid partId);
|
void partRemoved(QUuid partId);
|
||||||
void partPreviewChanged(QUuid partid);
|
void partPreviewChanged(QUuid partid);
|
||||||
void partLockStateChanged(QUuid partId);
|
void partLockStateChanged(QUuid partId);
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
#include "skeletongraphicswidget.h"
|
#include "skeletongraphicswidget.h"
|
||||||
#include "shortcuts.h"
|
#include "shortcuts.h"
|
||||||
#include "graphicscontainerwidget.h"
|
#include "graphicscontainerwidget.h"
|
||||||
|
#include "flowlayout.h"
|
||||||
|
#include "cutfacelistwidget.h"
|
||||||
|
|
||||||
PartWidget::PartWidget(const Document *document, QUuid partId) :
|
PartWidget::PartWidget(const Document *document, QUuid partId) :
|
||||||
m_document(document),
|
m_document(document),
|
||||||
|
@ -73,7 +75,7 @@ PartWidget::PartWidget(const Document *document, QUuid partId) :
|
||||||
initButton(m_colorButton);
|
initButton(m_colorButton);
|
||||||
|
|
||||||
m_cutRotationButton = new QPushButton;
|
m_cutRotationButton = new QPushButton;
|
||||||
m_cutRotationButton->setToolTip(tr("Cut rotation"));
|
m_cutRotationButton->setToolTip(tr("Cut face"));
|
||||||
m_cutRotationButton->setSizePolicy(retainSizePolicy);
|
m_cutRotationButton->setSizePolicy(retainSizePolicy);
|
||||||
initButton(m_cutRotationButton);
|
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::setPartChamferState, m_document, &Document::setPartChamferState);
|
||||||
connect(this, &PartWidget::setPartCutRotation, m_document, &Document::setPartCutRotation);
|
connect(this, &PartWidget::setPartCutRotation, m_document, &Document::setPartCutRotation);
|
||||||
connect(this, &PartWidget::setPartCutFace, m_document, &Document::setPartCutFace);
|
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::setPartColorState, m_document, &Document::setPartColorState);
|
||||||
connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId);
|
connect(this, &PartWidget::setPartMaterialId, m_document, &Document::setPartMaterialId);
|
||||||
connect(this, &PartWidget::checkPart, m_document, &Document::checkPart);
|
connect(this, &PartWidget::checkPart, m_document, &Document::checkPart);
|
||||||
|
@ -434,11 +437,16 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
||||||
rotationLayout->addWidget(rotationEraser);
|
rotationLayout->addWidget(rotationEraser);
|
||||||
rotationLayout->addWidget(rotationWidget);
|
rotationLayout->addWidget(rotationWidget);
|
||||||
|
|
||||||
QHBoxLayout *facesLayout = new QHBoxLayout;
|
QHBoxLayout *standardFacesLayout = new QHBoxLayout;
|
||||||
QPushButton *buttons[(int)CutFace::Count] = {0};
|
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) {
|
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];
|
auto button = buttons[i];
|
||||||
if (i == index) {
|
if (i == index) {
|
||||||
button->setFlat(true);
|
button->setFlat(true);
|
||||||
|
@ -448,8 +456,28 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
||||||
button->setEnabled(true);
|
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;
|
CutFace cutFace = (CutFace)i;
|
||||||
QString iconFilename = ":/resources/" + CutFaceToString(cutFace).toLower() + ".png";
|
QString iconFilename = ":/resources/" + CutFaceToString(cutFace).toLower() + ".png";
|
||||||
QPixmap pixmap(iconFilename);
|
QPixmap pixmap(iconFilename);
|
||||||
|
@ -462,7 +490,7 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
||||||
emit setPartCutFace(m_partId, cutFace);
|
emit setPartCutFace(m_partId, cutFace);
|
||||||
emit groupOperationAdded();
|
emit groupOperationAdded();
|
||||||
});
|
});
|
||||||
facesLayout->addWidget(button);
|
standardFacesLayout->addWidget(button);
|
||||||
buttons[i] = button;
|
buttons[i] = button;
|
||||||
}
|
}
|
||||||
updateCutFaceButtonState((size_t)part->cutFace);
|
updateCutFaceButtonState((size_t)part->cutFace);
|
||||||
|
@ -470,7 +498,8 @@ void PartWidget::showCutRotationSettingPopup(const QPoint &pos)
|
||||||
QVBoxLayout *popupLayout = new QVBoxLayout;
|
QVBoxLayout *popupLayout = new QVBoxLayout;
|
||||||
popupLayout->addLayout(rotationLayout);
|
popupLayout->addLayout(rotationLayout);
|
||||||
popupLayout->addSpacing(10);
|
popupLayout->addSpacing(10);
|
||||||
popupLayout->addLayout(facesLayout);
|
popupLayout->addLayout(standardFacesLayout);
|
||||||
|
popupLayout->addWidget(cutFaceListWidget);
|
||||||
|
|
||||||
popup->setLayout(popupLayout);
|
popup->setLayout(popupLayout);
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ signals:
|
||||||
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
void setPartColorState(QUuid partId, bool hasColor, QColor color);
|
||||||
void setPartCutRotation(QUuid partId, float cutRotation);
|
void setPartCutRotation(QUuid partId, float cutRotation);
|
||||||
void setPartCutFace(QUuid partId, CutFace cutFace);
|
void setPartCutFace(QUuid partId, CutFace cutFace);
|
||||||
|
void setPartCutFaceLinkedId(QUuid partId, QUuid linkedId);
|
||||||
void setPartMaterialId(QUuid partId, QUuid materialId);
|
void setPartMaterialId(QUuid partId, QUuid materialId);
|
||||||
void movePartUp(QUuid partId);
|
void movePartUp(QUuid partId);
|
||||||
void movePartDown(QUuid partId);
|
void movePartDown(QUuid partId);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
#include "meshloader.h"
|
#include "meshloader.h"
|
||||||
#include "cutface.h"
|
#include "cutface.h"
|
||||||
|
#include "parttarget.h"
|
||||||
|
|
||||||
class SkeletonNode
|
class SkeletonNode
|
||||||
{
|
{
|
||||||
|
@ -87,7 +88,9 @@ public:
|
||||||
bool dirty;
|
bool dirty;
|
||||||
float cutRotation;
|
float cutRotation;
|
||||||
CutFace cutFace;
|
CutFace cutFace;
|
||||||
|
QUuid cutFaceLinkedId;
|
||||||
QUuid materialId;
|
QUuid materialId;
|
||||||
|
PartTarget target;
|
||||||
SkeletonPart(const QUuid &withId=QUuid()) :
|
SkeletonPart(const QUuid &withId=QUuid()) :
|
||||||
visible(true),
|
visible(true),
|
||||||
locked(false),
|
locked(false),
|
||||||
|
@ -99,11 +102,12 @@ public:
|
||||||
deformWidth(1.0),
|
deformWidth(1.0),
|
||||||
rounded(false),
|
rounded(false),
|
||||||
chamfered(false),
|
chamfered(false),
|
||||||
color(Theme::white),
|
color(Qt::white),
|
||||||
hasColor(false),
|
hasColor(false),
|
||||||
dirty(true),
|
dirty(true),
|
||||||
cutRotation(0.0),
|
cutRotation(0.0),
|
||||||
cutFace(CutFace::Quad)
|
cutFace(CutFace::Quad),
|
||||||
|
target(PartTarget::Model)
|
||||||
{
|
{
|
||||||
id = withId.isNull() ? QUuid::createUuid() : withId;
|
id = withId.isNull() ? QUuid::createUuid() : withId;
|
||||||
}
|
}
|
||||||
|
@ -134,6 +138,16 @@ public:
|
||||||
void setCutFace(CutFace face)
|
void setCutFace(CutFace face)
|
||||||
{
|
{
|
||||||
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
|
bool deformThicknessAdjusted() const
|
||||||
{
|
{
|
||||||
|
@ -183,9 +197,11 @@ public:
|
||||||
hasColor = other.hasColor;
|
hasColor = other.hasColor;
|
||||||
cutRotation = other.cutRotation;
|
cutRotation = other.cutRotation;
|
||||||
cutFace = other.cutFace;
|
cutFace = other.cutFace;
|
||||||
|
cutFaceLinkedId = other.cutFaceLinkedId;
|
||||||
componentId = other.componentId;
|
componentId = other.componentId;
|
||||||
dirty = other.dirty;
|
dirty = other.dirty;
|
||||||
materialId = other.materialId;
|
materialId = other.materialId;
|
||||||
|
target = other.target;
|
||||||
}
|
}
|
||||||
void updatePreviewMesh(MeshLoader *previewMesh)
|
void updatePreviewMesh(MeshLoader *previewMesh)
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,7 @@ int Theme::miniIconFontSize = 0;
|
||||||
int Theme::miniIconSize = 0;
|
int Theme::miniIconSize = 0;
|
||||||
int Theme::partPreviewImageSize = 0;
|
int Theme::partPreviewImageSize = 0;
|
||||||
int Theme::materialPreviewImageSize = 0;
|
int Theme::materialPreviewImageSize = 0;
|
||||||
|
int Theme::cutFacePreviewImageSize = 0;
|
||||||
int Theme::posePreviewImageSize = 0;
|
int Theme::posePreviewImageSize = 0;
|
||||||
int Theme::motionPreviewImageSize = 0;
|
int Theme::motionPreviewImageSize = 0;
|
||||||
int Theme::sidebarPreferredWidth = 0;
|
int Theme::sidebarPreferredWidth = 0;
|
||||||
|
@ -54,6 +55,7 @@ void Theme::initAwsomeBaseSizes()
|
||||||
Theme::miniIconSize = (int)(Theme::miniIconFontSize * 1.67);
|
Theme::miniIconSize = (int)(Theme::miniIconFontSize * 1.67);
|
||||||
Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
Theme::partPreviewImageSize = (Theme::miniIconSize * 3);
|
||||||
Theme::materialPreviewImageSize = 75;
|
Theme::materialPreviewImageSize = 75;
|
||||||
|
Theme::cutFacePreviewImageSize = 75;
|
||||||
Theme::posePreviewImageSize = 75;
|
Theme::posePreviewImageSize = 75;
|
||||||
Theme::motionPreviewImageSize = 75;
|
Theme::motionPreviewImageSize = 75;
|
||||||
Theme::sidebarPreferredWidth = 200;
|
Theme::sidebarPreferredWidth = 200;
|
||||||
|
|
|
@ -35,6 +35,7 @@ public:
|
||||||
static int toolIconFontSize;
|
static int toolIconFontSize;
|
||||||
static int toolIconSize;
|
static int toolIconSize;
|
||||||
static int materialPreviewImageSize;
|
static int materialPreviewImageSize;
|
||||||
|
static int cutFacePreviewImageSize;
|
||||||
static int posePreviewImageSize;
|
static int posePreviewImageSize;
|
||||||
static int partPreviewImageSize;
|
static int partPreviewImageSize;
|
||||||
static int motionPreviewImageSize;
|
static int motionPreviewImageSize;
|
||||||
|
|
|
@ -616,7 +616,6 @@ void Builder::makeCut(const QVector3D &position,
|
||||||
const QVector3D &traverseDirection,
|
const QVector3D &traverseDirection,
|
||||||
std::vector<QVector3D> &resultCut)
|
std::vector<QVector3D> &resultCut)
|
||||||
{
|
{
|
||||||
baseNormal = revisedBaseNormalAcordingToCutNormal(baseNormal, cutNormal);
|
|
||||||
auto finalCutTemplate = cutTemplate;
|
auto finalCutTemplate = cutTemplate;
|
||||||
auto finalCutNormal = cutNormal;
|
auto finalCutNormal = cutNormal;
|
||||||
float degree = 0;
|
float degree = 0;
|
||||||
|
@ -624,14 +623,9 @@ void Builder::makeCut(const QVector3D &position,
|
||||||
degree = m_cutRotation * 180;
|
degree = m_cutRotation * 180;
|
||||||
}
|
}
|
||||||
if (QVector3D::dotProduct(cutNormal, traverseDirection) <= 0) {
|
if (QVector3D::dotProduct(cutNormal, traverseDirection) <= 0) {
|
||||||
baseNormal = -baseNormal;
|
|
||||||
finalCutNormal = -finalCutNormal;
|
finalCutNormal = -finalCutNormal;
|
||||||
std::reverse(finalCutTemplate.begin(), finalCutTemplate.end());
|
std::reverse(finalCutTemplate.begin(), finalCutTemplate.end());
|
||||||
degree = ((int)degree + 180) % 360;
|
std::rotate(finalCutTemplate.begin(), finalCutTemplate.begin() + finalCutTemplate.size() - 1, finalCutTemplate.end());
|
||||||
//for (auto &it: finalCutTemplate) {
|
|
||||||
// it.setX(-it.x());
|
|
||||||
// it.setY(-it.y());
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
QVector3D u = QVector3D::crossProduct(finalCutNormal, baseNormal).normalized();
|
QVector3D u = QVector3D::crossProduct(finalCutNormal, baseNormal).normalized();
|
||||||
QVector3D v = QVector3D::crossProduct(u, finalCutNormal).normalized();
|
QVector3D v = QVector3D::crossProduct(u, finalCutNormal).normalized();
|
||||||
|
|
Loading…
Reference in New Issue