Add user defined cut face

Extruding shape is totally customizable now, it's not limit to predefined shapes.
master
Jeremy Hu 2019-05-19 12:51:38 +09:30
parent fc341986c0
commit 8f5368a43f
26 changed files with 835 additions and 44 deletions

View File

@ -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

View File

@ -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);

View File

@ -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 &current = 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 &current = 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);
}

View File

@ -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

239
src/cutfacelistwidget.cpp Normal file
View File

@ -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();
}

41
src/cutfacelistwidget.h Normal file
View File

@ -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

60
src/cutfacewidget.cpp Normal file
View File

@ -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;
}

28
src/cutfacewidget.h Normal file
View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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:

View File

@ -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;
}

View File

@ -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);

View File

@ -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

5
src/parttarget.cpp Normal file
View File

@ -0,0 +1,5 @@
#include "parttarget.h"
IMPL_PartTargetFromString
IMPL_PartTargetToString
IMPL_PartTargetToDispName

50
src/parttarget.h Normal file
View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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();