summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@qt.io>2023-04-28 17:55:03 +0300
committerMiikka Heikkinen <miikka.heikkinen@qt.io>2023-05-05 09:38:53 +0000
commitdce30450a3762748535d1abcbe85ded60a05b6de (patch)
tree146d52a1a81a2688fd0046019d338dbbac610cb8
parentfe92d2ee5d35e5993673f98d8d5d6865c00fa95f (diff)
downloadqt-creator-dce30450a3762748535d1abcbe85ded60a05b6de.tar.gz
QmlDesigner: Add UI for lights baking setup
Instead of directly baking, bake lights action now opens baking setup dialog, where user can set up baking related properties for models and lights in the scene, including those exposed from subcomponents. Fixes: QDS-9805 Change-Id: I9e0bae98d8a79be4cf8b09ef989c41083a7fbf5d Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
-rw-r--r--share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsProgressDialog.qml (renamed from share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsDialog.qml)46
-rw-r--r--share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsSetupDialog.qml204
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt1
-rw-r--r--src/plugins/qmldesigner/components/edit3d/bakelights.cpp212
-rw-r--r--src/plugins/qmldesigner/components/edit3d/bakelights.h16
-rw-r--r--src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp357
-rw-r--r--src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.h51
-rw-r--r--src/plugins/qmldesigner/designercore/include/nodemetainfo.h1
-rw-r--r--src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp10
-rw-r--r--src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp3
-rw-r--r--src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h2
11 files changed, 813 insertions, 90 deletions
diff --git a/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsDialog.qml b/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsProgressDialog.qml
index 9632c0b4a1..18494d5f32 100644
--- a/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsDialog.qml
+++ b/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsProgressDialog.qml
@@ -14,6 +14,18 @@ Rectangle {
color: StudioTheme.Values.themePanelBackground
+ Connections {
+ target: rootView
+ function onProgress(msg) {
+ progressText.text += progressText.text === "" ? msg : "\n" + msg
+ scrollView.ensureVisible()
+ }
+
+ function onFinished() {
+ cancelButton.text = qsTr("Close")
+ }
+ }
+
Column {
id: col
padding: 5
@@ -29,8 +41,9 @@ Rectangle {
}
Rectangle {
+ id: rect
width: root.width - 16
- height: root.height - title.height - button.height - 20
+ height: root.height - title.height - cancelButton.height - 20
color: StudioTheme.Values.themePanelBackground
border.color: StudioTheme.Values.themeControlOutline
@@ -66,25 +79,24 @@ Rectangle {
}
- Connections {
- target: rootView
- function onProgress(msg) {
- progressText.text += progressText.text === "" ? msg : "\n" + msg
- scrollView.ensureVisible()
- }
+ Row {
+ spacing: StudioTheme.Values.dialogButtonSpacing
+ height: cancelButton.height
+ anchors.right: rect.right
- function onFinished() {
- button.text = qsTr("Close")
+ Button {
+ id: bakeAgainButton
+ text: qsTr("Bake Again")
+ anchors.margins: StudioTheme.Values.dialogButtonPadding
+ onClicked: rootView.rebake()
}
- }
- Button {
- id: button
- text: qsTr("Cancel")
- anchors.right: parent.right
- anchors.margins: StudioTheme.Values.dialogButtonPadding
-
- onClicked: rootView.cancel()
+ Button {
+ id: cancelButton
+ text: qsTr("Cancel")
+ anchors.margins: StudioTheme.Values.dialogButtonPadding
+ onClicked: rootView.cancel()
+ }
}
}
}
diff --git a/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsSetupDialog.qml b/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsSetupDialog.qml
new file mode 100644
index 0000000000..ab4d89374b
--- /dev/null
+++ b/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsSetupDialog.qml
@@ -0,0 +1,204 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtQuickDesignerTheme
+import HelperWidgets
+import StudioControls as StudioControls
+import StudioTheme as StudioTheme
+
+Rectangle {
+ id: root
+
+ color: StudioTheme.Values.themePanelBackground
+
+ Column {
+ id: col
+ padding: 5
+ leftPadding: 10
+ spacing: 5
+
+ Text {
+ id: title
+ text: qsTr("Lights baking setup for 3D view: %1").arg(sceneId)
+ font.bold: true
+ font.pixelSize: StudioTheme.Values.myFontSize
+ color: StudioTheme.Values.themeTextColor
+ }
+
+ Rectangle {
+ id: ctrlRect
+ width: root.width - 16
+ height: root.height - title.height - manualCheckBox.height - 20
+
+ color: StudioTheme.Values.themePanelBackground
+ border.color: StudioTheme.Values.themeControlOutline
+ border.width: StudioTheme.Values.border
+
+ ListView {
+ id: listView
+
+ anchors.fill: parent
+ anchors.margins: 4
+
+ clip: true
+
+ model: bakeModel
+ spacing: 5
+
+ delegate: Row {
+ spacing: 12
+
+ enabled: !manualCheckBox.checked
+
+ Text {
+ text: nodeId
+ color: StudioTheme.Values.themeTextColor
+ width: 200
+ clip: true
+ font.bold: isTitle
+ verticalAlignment: Qt.AlignVCenter
+ horizontalAlignment: Qt.AlignLeft
+ height: bakeModeCombo.height
+ leftPadding: isTitle ? 0 : 8
+ }
+
+ StudioControls.ComboBox {
+ id: bakeModeCombo
+ model: ListModel {
+ ListElement { text: qsTr("Baking Disabled"); value: "Light.BakeModeDisabled" }
+ ListElement { text: qsTr("Bake Indirect"); value: "Light.BakeModeIndirect" }
+ ListElement { text: qsTr("Bake All"); value: "Light.BakeModeAll" }
+ }
+
+ visible: !isModel && !isTitle
+ textRole: "text"
+ valueRole: "value"
+ currentIndex: bakeMode === "Light.BakeModeAll"
+ ? 2 : bakeMode === "Light.BakeModeIndirect" ? 1 : 0
+ actionIndicatorVisible: false
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("The baking mode applied to this light.")
+
+ onActivated: bakeMode = currentValue
+ }
+
+ StudioControls.CheckBox {
+ visible: isModel && !isTitle
+ checked: inUse
+ text: qsTr("In Use")
+ actionIndicatorVisible: false
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("If checked, this model contributes to baked lighting,\nfor example in form of casting shadows or indirect light.")
+
+ onToggled: inUse = checked
+ }
+
+ StudioControls.CheckBox {
+ visible: isModel && !isTitle
+ checked: isEnabled
+ text: qsTr("Enabled")
+ actionIndicatorVisible: false
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("If checked, baked lightmap texture is generated and rendered for this model.")
+
+ onToggled: isEnabled = checked
+ }
+
+ Row {
+ width: resolutionLabel.width + resolutionValue.width + StudioTheme.Values.sectionRowSpacing
+ spacing: StudioTheme.Values.sectionRowSpacing
+ visible: isModel && !isTitle
+ Text {
+ id: resolutionLabel
+ text: qsTr("Resolution:")
+ color: enabled ? StudioTheme.Values.themeTextColor
+ : StudioTheme.Values.themeTextColorDisabled
+ verticalAlignment: Qt.AlignVCenter
+ horizontalAlignment: Qt.AlignRight
+ height: resolutionValue.height
+ }
+
+ StudioControls.RealSpinBox {
+ id: resolutionValue
+ realFrom: 128
+ realTo: 128000
+ realValue: resolution
+ realStepSize: 128
+ width: 60
+ actionIndicatorVisible: false
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("Generated lightmap resolution for this model.")
+
+ onRealValueChanged: resolution = realValue
+ }
+ }
+ }
+ }
+ }
+
+ Item {
+ height: manualCheckBox.height
+ width: ctrlRect.width
+
+ StudioControls.CheckBox {
+ id: manualCheckBox
+ checked: false
+ text: qsTr("Setup baking manually")
+ actionIndicatorVisible: false
+
+ hoverEnabled: true
+ ToolTip.visible: hovered
+ ToolTip.text: qsTr("If checked, baking settings above are not applied on close or bake.\nInstead, user is expected to set baking properties manually.")
+ }
+
+ Row {
+ spacing: StudioTheme.Values.dialogButtonSpacing
+ height: manualCheckBox.height
+ anchors.right: parent.right
+
+ Button {
+ id: cancelButton
+ text: qsTr("Cancel")
+ anchors.margins: StudioTheme.Values.dialogButtonPadding
+ height: manualCheckBox.height
+ onClicked: rootView.cancel()
+ }
+
+ Button {
+ id: applyButton
+ text: qsTr("Apply & Close")
+ anchors.margins: StudioTheme.Values.dialogButtonPadding
+ height: manualCheckBox.height
+ onClicked: {
+ if (!manualCheckBox.checked)
+ rootView.apply()
+ rootView.cancel()
+ }
+ }
+
+ Button {
+ id: bakeButton
+ text: qsTr("Bake")
+ anchors.margins: StudioTheme.Values.dialogButtonPadding
+ height: manualCheckBox.height
+ onClicked: {
+ if (!manualCheckBox.checked)
+ rootView.apply()
+ rootView.bakeLights()
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 89bf1ff4e4..90155e9440 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -600,6 +600,7 @@ extend_qtc_plugin(QmlDesigner
edit3dvisibilitytogglesmenu.cpp edit3dvisibilitytogglesmenu.h
backgroundcolorselection.cpp backgroundcolorselection.h
bakelights.cpp bakelights.h
+ bakelightsdatamodel.cpp bakelightsdatamodel.h
bakelightsconnectionmanager.cpp bakelightsconnectionmanager.h
edit3d.qrc
)
diff --git a/src/plugins/qmldesigner/components/edit3d/bakelights.cpp b/src/plugins/qmldesigner/components/edit3d/bakelights.cpp
index c44b49bb5c..73e2f315b0 100644
--- a/src/plugins/qmldesigner/components/edit3d/bakelights.cpp
+++ b/src/plugins/qmldesigner/components/edit3d/bakelights.cpp
@@ -4,6 +4,7 @@
#include "bakelights.h"
#include "abstractview.h"
+#include "bakelightsdatamodel.h"
#include "bakelightsconnectionmanager.h"
#include "documentmanager.h"
#include "modelnode.h"
@@ -63,76 +64,19 @@ BakeLights::BakeLights(AbstractView *view)
return;
}
- // Create folders for lightmaps if they do not exist
- PropertyName loadPrefixPropName{"loadPrefix"};
- const QList<ModelNode> bakedLightmapNodes = m_view->allModelNodesOfType(
- m_view->model()->qtQuick3DBakedLightmapMetaInfo());
- Utils::FilePath currentPath = DocumentManager::currentFilePath().absolutePath();
- QSet<Utils::FilePath> pathSet;
- for (const ModelNode &node : bakedLightmapNodes) {
- if (node.hasVariantProperty(loadPrefixPropName)) {
- QString prefix = node.variantProperty(loadPrefixPropName).value().toString();
- Utils::FilePath fp = Utils::FilePath::fromString(prefix);
- if (fp.isRelativePath()) {
- fp = currentPath.pathAppended(prefix);
- if (!fp.exists())
- pathSet.insert(fp);
- }
- }
- }
- for (const Utils::FilePath &fp : std::as_const(pathSet))
- fp.createDir();
-
- // Show non-modal progress dialog with cancel button
- QString path = qmlSourcesPath() + "/BakeLightsDialog.qml";
-
- m_dialog = new QQuickView;
- m_dialog->setTitle(tr("Bake Lights"));
- m_dialog->setResizeMode(QQuickView::SizeRootObjectToView);
- m_dialog->setMinimumSize({150, 100});
- m_dialog->setWidth(800);
- m_dialog->setHeight(400);
- m_dialog->setFlags(Qt::Dialog);
- m_dialog->setModality(Qt::NonModal);
- m_dialog->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
-
- m_dialog->rootContext()->setContextProperties({
- {"rootView", QVariant::fromValue(this)},
- {"sceneId", QVariant::fromValue(m_view3dId)}
- });
- m_dialog->setSource(QUrl::fromLocalFile(path));
- m_dialog->installEventFilter(this);
- m_dialog->show();
-
- QTimer::singleShot(0, this, &BakeLights::bakeLights);
+ showSetupDialog();
}
BakeLights::~BakeLights()
{
- if (m_connectionManager) {
- m_connectionManager->setProgressCallback({});
- m_connectionManager->setFinishedCallback({});
- m_connectionManager->setCrashCallback({});
- }
-
- if (m_model) {
- m_model->setNodeInstanceView({});
- m_model->setRewriterView({});
- m_model.reset();
- }
-
- delete m_dialog;
- delete m_rewriterView;
- delete m_nodeInstanceView;
- delete m_connectionManager;
+ cleanup();
}
-QString BakeLights::resolveView3dId(AbstractView *view)
+ModelNode BakeLights::resolveView3dNode(AbstractView *view)
{
if (!view || !view->model())
return {};
- QString view3dId;
ModelNode activeView3D;
ModelNode activeScene = view->active3DSceneNode();
@@ -144,16 +88,26 @@ QString BakeLights::resolveView3dId(AbstractView *view)
if (sceneParent.metaInfo().isQtQuick3DView3D())
activeView3D = sceneParent;
}
- view3dId = activeView3D.id();
+ return activeView3D;
}
- return view3dId;
+ return {};
+}
+
+QString BakeLights::resolveView3dId(AbstractView *view)
+{
+ ModelNode activeView3D = resolveView3dNode(view);
+
+ if (activeView3D.isValid())
+ return activeView3D.id();
+
+ return {};
}
void BakeLights::raiseDialog()
{
- if (m_dialog)
- m_dialog->raise();
+ if (m_progressDialog)
+ m_progressDialog->raise();
}
void BakeLights::bakeLights()
@@ -161,6 +115,9 @@ void BakeLights::bakeLights()
if (!m_view || !m_view->model())
return;
+ m_setupDialog->hide();
+ showProgressDialog();
+
// Start baking process
m_connectionManager = new BakeLightsConnectionManager;
m_rewriterView = new RewriterView{m_view->externalDependencies(), RewriterView::Amend};
@@ -188,7 +145,7 @@ void BakeLights::bakeLights()
|| (!m_rewriterView->rootModelNode().metaInfo().isGraphicalItem() && !is3DRoot)) {
emit progress(tr("Invalid root node, baking aborted."));
emit finished();
- m_dialog->raise();
+ m_progressDialog->raise();
return;
}
@@ -199,7 +156,7 @@ void BakeLights::bakeLights()
};
auto finishedCallback = [this](const QString &msg) {
- m_dialog->raise();
+ m_progressDialog->raise();
emit progress(msg);
emit finished();
@@ -208,7 +165,7 @@ void BakeLights::bakeLights()
};
auto crashCallback = [this]() {
- m_dialog->raise();
+ m_progressDialog->raise();
emit progress(tr("Baking process crashed, baking aborted."));
emit finished();
};
@@ -224,17 +181,132 @@ void BakeLights::bakeLights()
m_nodeInstanceView->view3DAction(View3DActionType::SetBakeLightsView3D, m_view3dId);
}
+void BakeLights::apply()
+{
+ m_dataModel->apply();
+
+ // Create folders for lightmaps if they do not exist
+ PropertyName loadPrefixPropName{"loadPrefix"};
+ const QList<ModelNode> bakedLightmapNodes = m_view->allModelNodesOfType(
+ m_view->model()->qtQuick3DBakedLightmapMetaInfo());
+ Utils::FilePath currentPath = DocumentManager::currentFilePath().absolutePath();
+ QSet<Utils::FilePath> pathSet;
+ for (const ModelNode &node : bakedLightmapNodes) {
+ if (node.hasVariantProperty(loadPrefixPropName)) {
+ QString prefix = node.variantProperty(loadPrefixPropName).value().toString();
+ Utils::FilePath fp = Utils::FilePath::fromString(prefix);
+ if (fp.isRelativePath()) {
+ fp = currentPath.pathAppended(prefix);
+ if (!fp.exists())
+ pathSet.insert(fp);
+ }
+ }
+ }
+ for (const Utils::FilePath &fp : std::as_const(pathSet))
+ fp.createDir();
+}
+
+void BakeLights::rebake()
+{
+ QTimer::singleShot(0, this, [this]() {
+ cleanup();
+ showSetupDialog();
+ });
+}
+
+void BakeLights::showSetupDialog()
+{
+ if (!m_dataModel)
+ m_dataModel = new BakeLightsDataModel(m_view);
+
+ m_dataModel->reset();
+
+ if (!m_setupDialog) {
+ // Show non-modal progress dialog with cancel button
+ QString path = qmlSourcesPath() + "/BakeLightsSetupDialog.qml";
+
+ m_setupDialog = new QQuickView;
+ m_setupDialog->setTitle(tr("Lights Baking Setup"));
+ m_setupDialog->setResizeMode(QQuickView::SizeRootObjectToView);
+ m_setupDialog->setMinimumSize({550, 200});
+ m_setupDialog->setWidth(550);
+ m_setupDialog->setHeight(400);
+ m_setupDialog->setFlags(Qt::Dialog);
+ m_setupDialog->setModality(Qt::ApplicationModal);
+ m_setupDialog->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
+
+ m_setupDialog->rootContext()->setContextProperties({
+ {"rootView", QVariant::fromValue(this)},
+ {"sceneId", QVariant::fromValue(m_view3dId)},
+ {"bakeModel", QVariant::fromValue(m_dataModel.data())}
+ });
+ m_setupDialog->setSource(QUrl::fromLocalFile(path));
+ m_setupDialog->installEventFilter(this);
+ }
+ m_setupDialog->show();
+}
+
+void BakeLights::showProgressDialog()
+{
+ if (!m_progressDialog) {
+ // Show non-modal progress dialog with cancel button
+ QString path = qmlSourcesPath() + "/BakeLightsProgressDialog.qml";
+
+ m_progressDialog = new QQuickView;
+ m_progressDialog->setTitle(tr("Lights Baking Progress"));
+ m_progressDialog->setResizeMode(QQuickView::SizeRootObjectToView);
+ m_progressDialog->setMinimumSize({150, 100});
+ m_progressDialog->setWidth(800);
+ m_progressDialog->setHeight(400);
+ m_progressDialog->setFlags(Qt::Dialog);
+ m_progressDialog->setModality(Qt::NonModal);
+ m_progressDialog->engine()->addImportPath(propertyEditorResourcesPath() + "/imports");
+
+ m_progressDialog->rootContext()->setContextProperties({
+ {"rootView", QVariant::fromValue(this)},
+ {"sceneId", QVariant::fromValue(m_view3dId)}
+ });
+ m_progressDialog->setSource(QUrl::fromLocalFile(path));
+ m_progressDialog->installEventFilter(this);
+ }
+ m_progressDialog->show();
+}
+
+void BakeLights::cleanup()
+{
+ if (m_connectionManager) {
+ m_connectionManager->setProgressCallback({});
+ m_connectionManager->setFinishedCallback({});
+ m_connectionManager->setCrashCallback({});
+ }
+
+ if (m_model) {
+ m_model->setNodeInstanceView({});
+ m_model->setRewriterView({});
+ m_model.reset();
+ }
+
+ delete m_setupDialog;
+ delete m_progressDialog;
+ delete m_rewriterView;
+ delete m_nodeInstanceView;
+ delete m_connectionManager;
+ delete m_dataModel;
+}
+
void BakeLights::cancel()
{
- if (!m_dialog.isNull() && m_dialog->isVisible())
- m_dialog->close();
+ if (!m_setupDialog.isNull() && m_setupDialog->isVisible())
+ m_setupDialog->close();
+ if (!m_progressDialog.isNull() && m_progressDialog->isVisible())
+ m_progressDialog->close();
deleteLater();
}
bool BakeLights::eventFilter(QObject *obj, QEvent *event)
{
- if (obj == m_dialog) {
+ if (obj == m_progressDialog || obj == m_setupDialog) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape)
diff --git a/src/plugins/qmldesigner/components/edit3d/bakelights.h b/src/plugins/qmldesigner/components/edit3d/bakelights.h
index d63ca0b3b8..a219454076 100644
--- a/src/plugins/qmldesigner/components/edit3d/bakelights.h
+++ b/src/plugins/qmldesigner/components/edit3d/bakelights.h
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
#pragma once
+#include "modelnode.h"
#include "qmldesignercorelib_global.h"
#include <QObject>
@@ -17,6 +18,7 @@ class AbstractView;
class BakeLightsConnectionManager;
class NodeInstanceView;
class RewriterView;
+class BakeLightsDataModel;
class BakeLights : public QObject
{
@@ -27,9 +29,13 @@ public:
~BakeLights();
Q_INVOKABLE void cancel();
+ Q_INVOKABLE void bakeLights();
+ Q_INVOKABLE void apply();
+ Q_INVOKABLE void rebake();
void raiseDialog();
+ static ModelNode resolveView3dNode(AbstractView *view);
static QString resolveView3dId(AbstractView *view);
signals:
@@ -40,13 +46,19 @@ protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
- void bakeLights();
+ void showSetupDialog();
+ void showProgressDialog();
+ void cleanup();
+
+ // Separate dialogs for setup and progress, as setup needs to be modal
+ QPointer<QQuickView> m_setupDialog;
+ QPointer<QQuickView> m_progressDialog;
- QPointer<QQuickView> m_dialog;
QPointer<BakeLightsConnectionManager> m_connectionManager;
QPointer<NodeInstanceView> m_nodeInstanceView;
QPointer<RewriterView> m_rewriterView;
QPointer<AbstractView> m_view;
+ QPointer<BakeLightsDataModel> m_dataModel;
ModelPointer m_model;
QString m_view3dId;
};
diff --git a/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp b/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp
new file mode 100644
index 0000000000..e52d440377
--- /dev/null
+++ b/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.cpp
@@ -0,0 +1,357 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "bakelightsdatamodel.h"
+
+#include "abstractview.h"
+#include "bakelights.h"
+#include "bindingproperty.h"
+#include "enumeration.h"
+#include "model.h"
+#include "modelnode.h"
+#include "nodelistproperty.h"
+#include "nodemetainfo.h"
+#include "variantproperty.h"
+
+#include <utils/qtcassert.h>
+
+#include <algorithm>
+
+namespace QmlDesigner {
+
+
+BakeLightsDataModel::BakeLightsDataModel(AbstractView *view)
+ : QAbstractListModel(view)
+ , m_view(view)
+{
+}
+
+BakeLightsDataModel::~BakeLightsDataModel()
+{
+}
+
+int BakeLightsDataModel::rowCount(const QModelIndex &parent) const
+{
+ return m_dataList.count();
+}
+
+QHash<int, QByteArray> BakeLightsDataModel::roleNames() const
+{
+ static const QHash<int, QByteArray> roles {
+ {Qt::UserRole + 1, "nodeId"},
+ {Qt::UserRole + 2, "isModel"},
+ {Qt::UserRole + 3, "isEnabled"},
+ {Qt::UserRole + 4, "inUse"},
+ {Qt::UserRole + 5, "isTitle"},
+ {Qt::UserRole + 6, "resolution"},
+ {Qt::UserRole + 7, "bakeMode"}
+ };
+ return roles;
+}
+
+QVariant BakeLightsDataModel::data(const QModelIndex &index, int role) const
+{
+ QTC_ASSERT(index.isValid() && index.row() < m_dataList.count(), return {});
+ QTC_ASSERT(roleNames().contains(role), return {});
+
+ QByteArray roleName = roleNames().value(role);
+ const BakeData &bakeData = m_dataList[index.row()];
+
+ if (roleName == "nodeId") {
+ const QString id = bakeData.id;
+ const PropertyName aliasProp = bakeData.aliasProp;
+ if (aliasProp.isEmpty())
+ return id;
+ else
+ return QVariant{id + " - " + QString::fromUtf8(aliasProp)};
+ return {};
+ }
+
+ if (roleName == "isModel")
+ return bakeData.isModel;
+
+ if (roleName == "isEnabled")
+ return bakeData.enabled;
+
+ if (roleName == "inUse")
+ return bakeData.inUse;
+
+ if (roleName == "isTitle")
+ return bakeData.isTitle;
+
+ if (roleName == "resolution")
+ return bakeData.resolution;
+
+ if (roleName == "bakeMode")
+ return bakeData.bakeMode;
+
+ return {};
+}
+
+bool BakeLightsDataModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ QTC_ASSERT(index.isValid() && index.row() < m_dataList.count(), return false);
+ QTC_ASSERT(roleNames().contains(role), return false);
+
+ QByteArray roleName = roleNames().value(role);
+ BakeData &bakeData = m_dataList[index.row()];
+
+ bool changed = false;
+
+ if (roleName == "isEnabled") {
+ changed = bakeData.enabled != value.toBool();
+ bakeData.enabled = value.toBool();
+ } else if (roleName == "inUse") {
+ changed = bakeData.inUse != value.toBool();
+ bakeData.inUse = value.toBool();
+ } else if (roleName == "resolution") {
+ changed = bakeData.resolution != value.toInt();
+ bakeData.resolution = value.toInt();
+ } else if (roleName == "bakeMode") {
+ changed = bakeData.bakeMode != value.toString();
+ bakeData.bakeMode = value.toString();
+ }
+
+ if (changed)
+ emit dataChanged(index, index, {role});
+
+ return changed;
+}
+
+void BakeLightsDataModel::reset()
+{
+ if (!m_view || !m_view->model())
+ return;
+
+ beginResetModel();
+ m_dataList.clear();
+
+ ModelNode view3dNode = BakeLights::resolveView3dNode(m_view);
+
+ // Find all models and lights in active View3D
+ QList<ModelNode> nodes = view3dNode.allSubModelNodes();
+
+ if (view3dNode.hasBindingProperty("importScene"))
+ nodes.append(view3dNode.bindingProperty("importScene").resolveToModelNode().allSubModelNodesAndThisNode());
+
+ QList<BakeData> modelList;
+ QList<BakeData> lightList;
+ QList<BakeData> compModelList;
+ QList<BakeData> compLightList;
+
+ // Note: We are always loading base state values for baking. If users want to bake
+ // differently for different states, they need to setup things manually for now.
+ // Same goes if they want to use any unusual bindings in baking properties.
+ for (const auto &node : std::as_const(nodes)) {
+ BakeData data;
+ data.id = node.id();
+ if (data.id.isEmpty())
+ continue; // Skip nodes without id
+
+ if (node.metaInfo().isQtQuick3DModel()) {
+ data.isModel = true;
+ if (node.hasBindingProperty("bakedLightmap")) {
+ ModelNode blm = node.bindingProperty("bakedLightmap").resolveToModelNode();
+ if (blm.isValid()) {
+ if (blm.hasVariantProperty("enabled"))
+ data.enabled = blm.variantProperty("enabled").value().toBool();
+ else
+ data.enabled = true;
+ }
+ }
+ if (node.hasVariantProperty("lightmapBaseResolution"))
+ data.resolution = node.variantProperty("lightmapBaseResolution").value().toInt();
+ if (node.hasVariantProperty("usedInBakedLighting"))
+ data.inUse = node.variantProperty("usedInBakedLighting").value().toBool();
+ modelList.append(data);
+ } else if (node.metaInfo().isQtQuick3DLight()) {
+ if (node.hasVariantProperty("bakeMode")) {
+ data.bakeMode = node.variantProperty("bakeMode").value()
+ .value<QmlDesigner::Enumeration>().toString();
+ } else {
+ data.bakeMode = "Light.BakeModeDisabled";
+ }
+ lightList.append(data);
+ }
+
+ if (node.isComponent()) {
+ // Every component can expose multiple aliases
+ // We ignore baking properties defined inside the component (no visibility there)
+ const QList<AbstractProperty> props = node.properties();
+ PropertyMetaInfos metaInfos = node.metaInfo().properties();
+ for (const PropertyMetaInfo &mi : metaInfos) {
+ if (mi.isValid() && !mi.isPrivate() && mi.isWritable()) {
+ BakeData propData;
+ propData.id = data.id;
+ propData.aliasProp = mi.name();
+ if (mi.propertyType().isQtQuick3DModel()) {
+ propData.isModel = true;
+ PropertyName dotName = mi.name() + '.';
+ for (const AbstractProperty &prop : props) {
+ if (prop.name().startsWith(dotName)) {
+ PropertyName subName = prop.name().mid(dotName.size());
+ if (subName == "bakedLightmap") {
+ ModelNode blm = prop.toBindingProperty().resolveToModelNode();
+ if (blm.isValid()) {
+ if (blm.hasVariantProperty("enabled"))
+ propData.enabled = blm.variantProperty("enabled").value().toBool();
+ else
+ propData.enabled = true;
+ }
+ }
+ if (subName == "lightmapBaseResolution")
+ propData.resolution = prop.toVariantProperty().value().toInt();
+ if (subName == "usedInBakedLighting")
+ propData.inUse = prop.toVariantProperty().value().toBool();
+ }
+ }
+ compModelList.append(propData);
+ } else if (mi.propertyType().isQtQuick3DLight()) {
+ PropertyName dotName = mi.name() + '.';
+ for (const AbstractProperty &prop : props) {
+ if (prop.name().startsWith(dotName)) {
+ PropertyName subName = prop.name().mid(dotName.size());
+ if (subName == "bakeMode") {
+ propData.bakeMode = prop.toVariantProperty().value()
+ .value<QmlDesigner::Enumeration>()
+ .toString();
+ }
+ }
+ }
+ if (propData.bakeMode.isEmpty())
+ propData.bakeMode = "Light.BakeModeDisabled";
+ compLightList.append(propData);
+ }
+ }
+ }
+ }
+ }
+
+ auto sortList = [](QList<BakeData> &list) {
+ std::sort(list.begin(), list.end(), [](const BakeData &a, const BakeData &b) {
+ return a.id.compare(b.id) < 0;
+ });
+ };
+
+ sortList(modelList);
+ sortList(lightList);
+ sortList(compModelList);
+ sortList(compLightList);
+
+ BakeData titleData;
+ titleData.isTitle = true;
+ titleData.id = tr("Lights");
+ m_dataList.append(titleData);
+ m_dataList.append(lightList);
+ m_dataList.append(compLightList);
+ titleData.id = tr("Models");
+ m_dataList.append(titleData);
+ m_dataList.append(modelList);
+ m_dataList.append(compModelList);
+
+ endResetModel();
+}
+
+void BakeLightsDataModel::apply()
+{
+ if (!m_view || !m_view->model())
+ return;
+
+ auto setBakedLightmap = [this](const ModelNode &node, const BakeData &data) {
+ ModelNode blmNode;
+ PropertyName propName{"bakedLightmap"};
+ if (!data.aliasProp.isEmpty())
+ propName.prepend(data.aliasProp + '.');
+ if (node.hasBindingProperty(propName))
+ blmNode = node.bindingProperty(propName).resolveToModelNode();
+ if (!blmNode.isValid() && data.enabled) {
+ NodeMetaInfo metaInfo = m_view->model()->qtQuick3DBakedLightmapMetaInfo();
+ blmNode = m_view->createModelNode("QtQuick3D.BakedLightmap",
+ metaInfo.majorVersion(),
+ metaInfo.minorVersion());
+ QString idPart;
+ if (data.aliasProp.isEmpty())
+ idPart = data.id;
+ else
+ idPart = QStringLiteral("%1_%2").arg(data.id, QString::fromUtf8(data.aliasProp));
+ QString newId = m_view->model()->generateNewId(QStringLiteral("blm_%1").arg(idPart));
+ blmNode.setIdWithoutRefactoring(newId);
+ node.defaultNodeListProperty().reparentHere(blmNode);
+ node.bindingProperty(propName).setExpression(newId);
+ }
+ if (blmNode.isValid()) {
+ VariantProperty enabledProp = blmNode.variantProperty("enabled");
+ VariantProperty prefixProp = blmNode.variantProperty("loadPrefix");
+ VariantProperty keyProp = blmNode.variantProperty("key");
+ enabledProp.setValue(data.enabled);
+ prefixProp.setValue(commonPrefix());
+ keyProp.setValue(blmNode.id());
+ }
+ };
+
+ auto setVariantProp = [](const ModelNode &node,
+ const PropertyName &propName,
+ const PropertyName &aliasProp,
+ const QVariant &value,
+ const QVariant &defaultValue) {
+ PropertyName resolvedName = propName;
+ if (!aliasProp.isEmpty())
+ resolvedName.prepend(aliasProp + '.');
+ if (node.hasVariantProperty(resolvedName) || value != defaultValue)
+ node.variantProperty(resolvedName).setValue(value);
+ };
+
+ auto setResolution = [setVariantProp](const ModelNode &node, const BakeData &data) {
+ setVariantProp(node, "lightmapBaseResolution", data.aliasProp, data.resolution, 1024);
+ };
+
+ auto setInUse = [setVariantProp](const ModelNode &node, const BakeData &data) {
+ setVariantProp(node, "usedInBakedLighting", data.aliasProp, data.inUse, false);
+ };
+
+ auto setBakeMode = [setVariantProp](const ModelNode &node, const BakeData &data) {
+ setVariantProp(node, "bakeMode", data.aliasProp,
+ QVariant::fromValue(QmlDesigner::Enumeration(data.bakeMode)),
+ QVariant::fromValue(QmlDesigner::Enumeration("Light", "BakeModeDisabled")));
+ };
+
+ // Commits changes to scene model
+ m_view->executeInTransaction(__FUNCTION__, [&]() {
+ for (const BakeData &data : std::as_const(m_dataList)) {
+ if (data.isTitle)
+ continue;
+ ModelNode node = m_view->modelNodeForId(data.id);
+ if (data.isModel) {
+ setBakedLightmap(node, data);
+ setResolution(node, data);
+ setInUse(node, data);
+ } else {
+ setBakeMode(node, data);
+ }
+ }
+ });
+}
+
+QString BakeLightsDataModel::commonPrefix()
+{
+ static QString prefix = "lightmaps";
+ return prefix;
+}
+
+QDebug operator<<(QDebug debug, const BakeLightsDataModel::BakeData &data)
+{
+ QDebugStateSaver saver(debug);
+ debug.space() << "("
+ << "id:" << data.id
+ << "aliasProp:" << data.aliasProp
+ << "isModel:" << data.isModel
+ << "enabled:" << data.enabled
+ << "inUse:" << data.inUse
+ << "resolution:" << data.resolution
+ << "bakeMode:" << data.bakeMode
+ << "isTitle:" << data.isTitle
+ << ")";
+ return debug;
+}
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.h b/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.h
new file mode 100644
index 0000000000..9217ae44bb
--- /dev/null
+++ b/src/plugins/qmldesigner/components/edit3d/bakelightsdatamodel.h
@@ -0,0 +1,51 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+#pragma once
+
+#include "qmldesignercorelib_global.h"
+
+#include <QAbstractListModel>
+#include <QObject>
+#include <QPointer>
+
+namespace QmlDesigner {
+
+class AbstractView;
+
+class BakeLightsDataModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ struct BakeData {
+ QString id; // node id. Also used as BakedLightmap.key
+ PropertyName aliasProp; // property id for component exposed models/lights
+ bool isModel = false; // false means light
+ bool enabled = false;
+ bool inUse = false;
+ bool isTitle = false; // if true, indicates a title row in UI
+ int resolution = 1024;
+ QString bakeMode;
+ };
+
+ BakeLightsDataModel(AbstractView *view);
+ ~BakeLightsDataModel() override;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QHash<int, QByteArray> roleNames() const override;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
+
+ void reset();
+ void apply();
+
+private:
+ QString commonPrefix();
+
+ QPointer<AbstractView> m_view;
+ QList<BakeData> m_dataList;
+};
+
+QDebug operator<<(QDebug debug, const BakeLightsDataModel::BakeData &data);
+
+} // namespace QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h
index 4c692565fd..8642a03875 100644
--- a/src/plugins/qmldesigner/designercore/include/nodemetainfo.h
+++ b/src/plugins/qmldesigner/designercore/include/nodemetainfo.h
@@ -124,6 +124,7 @@ public:
bool isQtQuick3DEffect() const;
bool isQtQuick3DInstanceList() const;
bool isQtQuick3DInstanceListEntry() const;
+ bool isQtQuick3DLight() const;
bool isQtQuick3DMaterial() const;
bool isQtQuick3DModel() const;
bool isQtQuick3DNode() const;
diff --git a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp
index aafb355b68..770d919d71 100644
--- a/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp
+++ b/src/plugins/qmldesigner/designercore/metainfo/nodemetainfo.cpp
@@ -2265,6 +2265,16 @@ bool NodeMetaInfo::isQtQuick3DInstanceListEntry() const
}
}
+bool NodeMetaInfo::isQtQuick3DLight() const
+{
+ if constexpr (useProjectStorage()) {
+ using namespace Storage::Info;
+ return isBasedOnCommonType<QtQuick3D, Light>(m_projectStorage, m_typeId);
+ } else {
+ return isValid() && isSubclassOf("QtQuick3D.Light");
+ }
+}
+
bool NodeMetaInfo::isQtQuick3DInstanceList() const
{
if constexpr (useProjectStorage()) {
diff --git a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
index 493c834885..fe9db55ce6 100644
--- a/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
+++ b/src/plugins/qmldesigner/designercore/model/texttomodelmerger.cpp
@@ -101,7 +101,8 @@ QStringList knownEnumScopes()
"Grid",
"ItemLayer",
"ImageLayer",
- "SpriteLayer"};
+ "SpriteLayer",
+ "Light"};
return list;
}
diff --git a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h
index 3cd7f3a016..eda1daba5f 100644
--- a/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h
+++ b/src/plugins/qmldesigner/designercore/projectstorage/commontypecache.h
@@ -49,6 +49,7 @@ inline constexpr char GroupItem[] = "GroupItem";
inline constexpr char Image[] = "Image";
inline constexpr char InstanceListEntry[] = "InstanceListEntry";
inline constexpr char InstanceList[] = "InstanceList";
+inline constexpr char Light[] = "Light";
inline constexpr char IntType[] = "int";
inline constexpr char Item[] = "Item";
inline constexpr char KeyframeGroup[] = "KeyframeGroup";
@@ -194,6 +195,7 @@ class CommonTypeCache
CacheType<QtQuick3D, Effect>,
CacheType<QtQuick3D, InstanceList>,
CacheType<QtQuick3D, InstanceListEntry>,
+ CacheType<QtQuick3D, Light>,
CacheType<QtQuick3D, Material>,
CacheType<QtQuick3D, Model>,
CacheType<QtQuick3D, Node>,