New functionalities implementation

Create QML source as component (new file).
Generate "in" ports dynamically based on metainfo (texture).

Change-Id: I795e4ccb37eebec40e9716057b71a50073239ace
Reviewed-by: Rafal Andrusieczko <rnd@spyro-soft.com>
This commit is contained in:
Rafal Andrusieczko
2024-12-18 19:40:35 +01:00
parent db42dde3b4
commit af4a816086
7 changed files with 264 additions and 22 deletions

View File

@@ -164,6 +164,9 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
onSave: { onSave: {
graphView.graph.clearGraph();
graphView.graph.insertNode(Nodes.Components.material);
NodeGraphEditorBackend.nodeGraphEditorModel.createQmlComponent(graphView.graph);
updateGraphData(); updateGraphData();
NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(fileName); NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(fileName);
NodeGraphEditorBackend.nodeGraphEditorModel.openFileName(fileName); NodeGraphEditorBackend.nodeGraphEditorModel.openFileName(fileName);
@@ -182,6 +185,8 @@ Item {
onRejected: {} onRejected: {}
onSave: { onSave: {
if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") { if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") {
NodeGraphEditorBackend.nodeGraphEditorModel.createQmlComponent(graphView.graph);
/*Save current graph data to the backend*/ /*Save current graph data to the backend*/
updateGraphData(); updateGraphData();
@@ -229,17 +234,7 @@ Item {
tooltip: qsTr("Add a new graph node.") tooltip: qsTr("Add a new graph node.")
onClicked: () => { onClicked: () => {
newNodeGraphDialog.open(); saveAsDialog.open();
}
}
HelperWidgets.AbstractButton {
buttonIcon: StudioTheme.Constants.remove_medium
style: StudioTheme.Values.viewBarButtonStyle
tooltip: qsTr("Clear graph.")
onClicked: () => {
graphView.graph.clearGraph();
} }
} }
@@ -251,6 +246,7 @@ Item {
onClicked: () => { onClicked: () => {
if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") { if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") {
NodeGraphEditorBackend.nodeGraphEditorModel.createQmlComponent(graphView.graph);
updateGraphData(); updateGraphData();
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = false; NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = false;
NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName); NodeGraphEditorBackend.nodeGraphEditorModel.saveFile(NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName);
@@ -260,16 +256,6 @@ Item {
} }
} }
HelperWidgets.AbstractButton {
buttonIcon: StudioTheme.Constants.saveAs_medium
style: StudioTheme.Values.viewBarButtonStyle
tooltip: qsTr("Save as ...")
onClicked: () => {
saveAsDialog.open();
}
}
Label { Label {
text: NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName text: NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName
} }
@@ -299,7 +285,9 @@ Item {
} }
} }
onRightClicked: function (pos) { onRightClicked: function (pos) {
contextMenu.popup(); if (NodeGraphEditorBackend.nodeGraphEditorModel.currentFileName !== "") {
contextMenu.popup();
}
} }
} }

View File

@@ -60,6 +60,30 @@ StudioControls.Dialog {
} }
} }
Row {
Text {
anchors.verticalCenter: parent.verticalCenter
color: StudioTheme.Values.themeTextColor
text: qsTr("Type: ")
}
StudioControls.ComboBox {
id: cbType
actionIndicatorVisible: false
model: [
{
value: "principled_material",
text: "Principled Material"
},
]
textRole: "text"
valueRole: "value"
onActivated: () => {}
}
}
Text { Text {
id: emptyText id: emptyText

View File

@@ -24,6 +24,7 @@ Base {
Component.onCompleted: { Component.onCompleted: {
node.label = "Texture"; node.label = "Texture";
internal.configurePorts(root.graph);
} }
onValueChanged: { onValueChanged: {
NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true; NodeGraphEditorBackend.nodeGraphEditorModel.hasUnsavedChanges = true;
@@ -33,6 +34,23 @@ Base {
anchors.centerIn: parent anchors.centerIn: parent
height: 96 height: 96
source: root.value.source source: root.value.source
// source: `image://qmldesigner_nodegrapheditor/${root.value.source}`
width: 96 width: 96
} }
QtObject {
id: internal
function configurePorts(graph) {
const inPorts = NodeGraphEditorBackend.widget.createMetaData_inPorts("QtQuick3D.Texture");
inPorts.forEach(data => {
const portItem = graph.insertPort(root.node, Qan.NodeItem.Left, Qan.PortItem.In, `${data.displayName} (${data.type})`, data.id);
portItem.dataName = data.displayName;
portItem.dataType = data.type;
});
const valuePort = graph.insertPort(root.node, Qan.NodeItem.Right, Qan.PortItem.Out, "OUT", "texture");
valuePort.dataType = "Texture";
}
}
} }

View File

@@ -7,9 +7,38 @@
#include "nodegrapheditorview.h" #include "nodegrapheditorview.h"
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <QFile>
#include <QFileInfo> #include <QFileInfo>
#include <QList>
#include <QMap>
#include <QString>
#include <QuickQanava>
namespace QmlDesigner { namespace QmlDesigner {
enum class FileType
{
Binary,
Text
};
static bool writeToFile(const QByteArray &buf, const QString &filename, FileType fileType)
{
QDir().mkpath(QFileInfo(filename).path());
QFile f(filename);
QIODevice::OpenMode flags = QIODevice::WriteOnly | QIODevice::Truncate;
if (fileType == FileType::Text)
flags |= QIODevice::Text;
if (!f.open(flags)) {
qWarning() << "Failed to open file for writing:" << filename;
return false;
}
f.write(buf);
return true;
}
NodeGraphEditorModel::NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView) NodeGraphEditorModel::NodeGraphEditorModel(NodeGraphEditorView *nodeGraphEditorView)
: QStandardItemModel(nodeGraphEditorView) : QStandardItemModel(nodeGraphEditorView)
, m_editorView(nodeGraphEditorView) , m_editorView(nodeGraphEditorView)
@@ -125,4 +154,134 @@ void NodeGraphEditorModel::setHasUnsavedChanges(bool val)
}; };
QString NodeGraphEditorModel::graphData(){return m_graphData;} QString NodeGraphEditorModel::graphData(){return m_graphData;}
void NodeGraphEditorModel::createQmlComponent(qan::Graph* graph)
{
qan::Node* root = nullptr;
for ( const auto& node : std::as_const(graph->get_nodes()) ) {
const QString className = QString::fromUtf8( node->getItem()->metaObject()->className() );
if (className.startsWith("Material")) {
root = node;
break;
}
}
qCritical() << "createQmlComponent" << root;
if (!root)
return;
int indentation = 1;
int textureCounter = 0;
QMap<QString, QString> translator;
QString s;
const QList<QString> allowedTypes{
"nge::BaseColor",
"nge::Metalness",
"nge::Roughness",
"Texture",
};
const QList<QString> requireQuotation{
"QColor",
"QUrl",
};
const auto makeIndent = [](int v){
QString s;
for (int i = 0; i < v; i++) {
s += " ";
}
return s;
};
const std::function<void(qan::Node* node)> scan = [&allowedTypes, &scan, &s, &translator, &requireQuotation, &textureCounter, &indentation, &makeIndent](qan::Node* node){
for ( const auto& qi : std::as_const( node->getItem()->getPorts() ) ) {
const auto port = qobject_cast<qan::PortItem*>(qi);
if ( port->getInEdgeItems().size() > 0 ) {
const auto dataName = port->property("dataName").toString();
const auto dataType = port->property("dataType").toString();
const auto edge = port->getInEdgeItems().at(port->getInEdgeItems().size() - 1)->getEdge();
if (!edge) {
continue;
}
if ( dataType == "nge::BaseColor" ) {
translator = {
{"color", "baseColor"},
{"channel", "baseColorChannel"},
{"map", "baseColorMap"},
{"singleChannelEnabled", "baseColorSingleChannelEnabled"},
};
}
else if ( dataType == "nge::Metalness" ) {
translator = {
{"channel", "metalnessChannel"},
{"map", "metalnessMap"},
{"metalness", "metalness"},
};
}
else if ( dataType == "nge::Roughness" ) {
translator = {
{"channel", "roughnessChannel"},
{"map", "roughnessMap"},
{"roughness", "roughness"},
};
}
if ( dataName != "" ) {
QObject* obj = edge->getDestination()->getItem()->property("value").value<QObject*>();
const auto value = obj->property(dataName.toUtf8().constData());
QString valueString = requireQuotation.contains(dataType) ? QString{"\"%1\""}.arg(value.toString()) : value.toString();
if (valueString.isEmpty() && dataType == "Texture") {
valueString=QString{"texture_%1"}.arg(textureCounter++);
}
valueString.remove("image://qmldesigner_nodegrapheditor/");
const QString key = translator.contains(dataName) ? translator[dataName] : dataName;
s += QString{"%1%2: %3\n"}.arg(makeIndent(indentation), key, valueString);
if ( dataType == "Texture" ) {
s += QString{"%1Texture {\n"}.arg(makeIndent(indentation++));
s += QString{"%1id: %2\n"}.arg(makeIndent(indentation), valueString);
}
}
if (allowedTypes.contains(dataType)) {
scan(edge->getSource());
}
if ( dataType == "Texture" ) {
s += QString{"%1}\n"}.arg(makeIndent(--indentation));
}
}
}
};
s += QString{
R"(import QtQuick
import QtQuick3D
PrincipledMaterial {
id: root
)"
};
scan(root);
s += QString{
R"(}
)"
};
Utils::FilePath path = DocumentManager::currentResourcePath().pathAppended(m_currentFileName + ".qml");
qCritical() << path.toString();
writeToFile(s.toUtf8(), path.toString(), FileType::Text);
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -6,6 +6,10 @@
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QPointer> #include <QPointer>
namespace qan {
class Graph;
}
namespace QmlDesigner { namespace QmlDesigner {
class NodeGraphEditorView; class NodeGraphEditorView;
@@ -19,6 +23,7 @@ public:
Q_INVOKABLE void openFile(QString filePath); Q_INVOKABLE void openFile(QString filePath);
Q_INVOKABLE void openFileName(QString filePath); Q_INVOKABLE void openFileName(QString filePath);
Q_INVOKABLE void saveFile(QString fileName); Q_INVOKABLE void saveFile(QString fileName);
Q_INVOKABLE void createQmlComponent(qan::Graph* graph);
private: private:
QPointer<NodeGraphEditorView> m_editorView; QPointer<NodeGraphEditorView> m_editorView;

View File

@@ -7,6 +7,7 @@
#include "nodegrapheditormodel.h" #include "nodegrapheditormodel.h"
#include "nodegrapheditorview.h" #include "nodegrapheditorview.h"
#include <nodemetainfo.h>
#include <qmldesignerconstants.h> #include <qmldesignerconstants.h>
#include <qmldesignerplugin.h> #include <qmldesignerplugin.h>
#include <theme.h> #include <theme.h>
@@ -105,6 +106,52 @@ QString NodeGraphEditorWidget::qmlSourcesPath()
return Core::ICore::resourcePath("qmldesigner/nodegrapheditor").toString(); return Core::ICore::resourcePath("qmldesigner/nodegrapheditor").toString();
} }
static QList<QString> allowedTypes {
"bool",
"float",
"double",
"QColor",
"QUrl",
"Texture",
};
static QMap<QString, QString> translateTypes {
{ "float", "real" },
{ "double", "real" },
};
static QList<QString> omitNames {
};
QList<QVariantMap> NodeGraphEditorWidget::createMetaData_inPorts(QByteArray typeName)
{
QList<QVariantMap> result;
const auto mi = m_editorView->model()->metaInfo(typeName);
if (mi.isValid()) {
for (const auto& property : mi.properties()) {
QString type = QString::fromUtf8(property.propertyType().simplifiedTypeName());
const QString name = QString::fromUtf8(property.name());
if (!allowedTypes.contains(type))
continue;
if (omitNames.contains(name))
continue;
if (translateTypes.contains(type))
type = translateTypes[type];
result.append({
{ "id", name },
{ "displayName", name },
{ "type", type }
});
}
}
return result;
}
QString NodeGraphEditorWidget::generateUUID() const QString NodeGraphEditorWidget::generateUUID() const
{ {
return QUuid::createUuid().toString(); return QUuid::createUuid().toString();

View File

@@ -30,6 +30,7 @@ public:
NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView, NodeGraphEditorModel *nodeGraphEditorModel); NodeGraphEditorWidget(NodeGraphEditorView *nodeGraphEditorView, NodeGraphEditorModel *nodeGraphEditorModel);
~NodeGraphEditorWidget() override = default; ~NodeGraphEditorWidget() override = default;
Q_INVOKABLE QList<QVariantMap> createMetaData_inPorts(QByteArray typeName);
Q_INVOKABLE QString generateUUID() const; Q_INVOKABLE QString generateUUID() const;
static QString qmlSourcesPath(); static QString qmlSourcesPath();