diff --git a/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsDialog.qml b/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsDialog.qml new file mode 100644 index 00000000000..9632c0b4a11 --- /dev/null +++ b/share/qtcreator/qmldesigner/edit3dQmlSource/BakeLightsDialog.qml @@ -0,0 +1,90 @@ +// 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("Baking lights for 3D view: %1").arg(sceneId) + font.bold: true + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeTextColor + } + + Rectangle { + width: root.width - 16 + height: root.height - title.height - button.height - 20 + + color: StudioTheme.Values.themePanelBackground + border.color: StudioTheme.Values.themeControlOutline + border.width: StudioTheme.Values.border + + ScrollView { + id: scrollView + + anchors.fill: parent + anchors.margins: 4 + + clip: true + + Behavior on contentY { + PropertyAnimation { + easing.type: Easing.InOutQuad + } + } + + Text { + id: progressText + width: scrollView.width + font.pixelSize: StudioTheme.Values.myFontSize + color: StudioTheme.Values.themeTextColor + } + + function ensureVisible() + { + let newPos = scrollView.contentHeight - scrollView.height + scrollView.contentY = newPos < 0 ? 0 : newPos + } + } + + } + + Connections { + target: rootView + function onProgress(msg) { + progressText.text += progressText.text === "" ? msg : "\n" + msg + scrollView.ensureVisible() + } + + function onFinished() { + button.text = qsTr("Close") + } + } + + Button { + id: button + text: qsTr("Cancel") + anchors.right: parent.right + anchors.margins: StudioTheme.Values.dialogButtonPadding + + onClicked: rootView.cancel() + } + } +} diff --git a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h index 5c8cd3af063..30b0cce9981 100644 --- a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h +++ b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h @@ -19,7 +19,11 @@ public: RenderModelNodePreviewImage, Import3DSupport, NodeAtPos, - None }; + BakeLightsProgress, + BakeLightsFinished, + BakeLightsAborted, + None + }; PuppetToCreatorCommand(Type type, const QVariant &data); PuppetToCreatorCommand() = default; diff --git a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h index 57abf115e17..67f0fa4c5c2 100644 --- a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h +++ b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h @@ -48,7 +48,8 @@ enum class View3DActionType { SelectGridColor, ResetBackgroundColor, SyncBackgroundColor, - GetNodeAtPos + GetNodeAtPos, + SetBakeLightsView3D }; constexpr bool isNanotraceEnabled() diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 00714ca6cc6..9280965ce0d 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -653,6 +653,8 @@ extend_qtc_plugin(QmlDesigner edit3dactions.cpp edit3dactions.h edit3dvisibilitytogglesmenu.cpp edit3dvisibilitytogglesmenu.h backgroundcolorselection.cpp backgroundcolorselection.h + bakelights.cpp bakelights.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 new file mode 100644 index 00000000000..c44b49bb5c4 --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/bakelights.cpp @@ -0,0 +1,250 @@ +// 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 "bakelights.h" + +#include "abstractview.h" +#include "bakelightsconnectionmanager.h" +#include "documentmanager.h" +#include "modelnode.h" +#include "nodeabstractproperty.h" +#include "nodeinstanceview.h" +#include "nodemetainfo.h" +#include "plaintexteditmodifier.h" +#include "rewriterview.h" +#include "variantproperty.h" + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace QmlDesigner { + +static QString propertyEditorResourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/propertyEditorQmlSources"; +#endif + return Core::ICore::resourcePath("qmldesigner/propertyEditorQmlSources").toString(); +} + +static QString qmlSourcesPath() +{ +#ifdef SHARE_QML_PATH + if (Utils::qtcEnvironmentVariableIsSet("LOAD_QML_FROM_SOURCE")) + return QLatin1String(SHARE_QML_PATH) + "/edit3dQmlSource"; +#endif + return Core::ICore::resourcePath("qmldesigner/edit3dQmlSource").toString(); +} + +BakeLights::BakeLights(AbstractView *view) + : QObject(view) + , m_view(view) +{ + m_view3dId = resolveView3dId(view); + + if (m_view3dId.isEmpty()) { + // It should never get here, baking controls should be disabled in this case + qWarning() << __FUNCTION__ << "Active scene is not View3D"; + deleteLater(); + return; + } + + // Create folders for lightmaps if they do not exist + PropertyName loadPrefixPropName{"loadPrefix"}; + const QList bakedLightmapNodes = m_view->allModelNodesOfType( + m_view->model()->qtQuick3DBakedLightmapMetaInfo()); + Utils::FilePath currentPath = DocumentManager::currentFilePath().absolutePath(); + QSet 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); +} + +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; +} + +QString BakeLights::resolveView3dId(AbstractView *view) +{ + if (!view || !view->model()) + return {}; + + QString view3dId; + ModelNode activeView3D; + ModelNode activeScene = view->active3DSceneNode(); + + if (activeScene.isValid()) { + if (activeScene.metaInfo().isQtQuick3DView3D()) { + activeView3D = activeScene; + } else { + ModelNode sceneParent = activeScene.parentProperty().parentModelNode(); + if (sceneParent.metaInfo().isQtQuick3DView3D()) + activeView3D = sceneParent; + } + view3dId = activeView3D.id(); + } + + return view3dId; +} + +void BakeLights::raiseDialog() +{ + if (m_dialog) + m_dialog->raise(); +} + +void BakeLights::bakeLights() +{ + if (!m_view || !m_view->model()) + return; + + // Start baking process + m_connectionManager = new BakeLightsConnectionManager; + m_rewriterView = new RewriterView{m_view->externalDependencies(), RewriterView::Amend}; + m_nodeInstanceView = new NodeInstanceView{*m_connectionManager, m_view->externalDependencies()}; + + m_model = QmlDesigner::Model::create("QtQuick/Item", 2, 1); + m_model->setFileUrl(m_view->model()->fileUrl()); + + // Take the current unsaved state of the main model and apply it to our copy + auto textDocument = std::make_unique( + m_view->model()->rewriterView()->textModifier()->textDocument()->toRawText()); + + auto modifier = std::make_unique(textDocument.get(), + QTextCursor{textDocument.get()}); + + m_rewriterView->setTextModifier(modifier.get()); + m_model->setRewriterView(m_rewriterView); + + auto rootModelNodeMetaInfo = m_rewriterView->rootModelNode().metaInfo(); + bool is3DRoot = m_rewriterView->errors().isEmpty() + && (rootModelNodeMetaInfo.isQtQuick3DNode() + || rootModelNodeMetaInfo.isQtQuick3DMaterial()); + + if (!m_rewriterView->errors().isEmpty() + || (!m_rewriterView->rootModelNode().metaInfo().isGraphicalItem() && !is3DRoot)) { + emit progress(tr("Invalid root node, baking aborted.")); + emit finished(); + m_dialog->raise(); + return; + } + + m_nodeInstanceView->setTarget(m_view->nodeInstanceView()->target()); + + auto progressCallback = [this](const QString &msg) { + emit progress(msg); + }; + + auto finishedCallback = [this](const QString &msg) { + m_dialog->raise(); + emit progress(msg); + emit finished(); + + // Puppet reset is needed to update baking results to current views + m_view->resetPuppet(); + }; + + auto crashCallback = [this]() { + m_dialog->raise(); + emit progress(tr("Baking process crashed, baking aborted.")); + emit finished(); + }; + + m_connectionManager->setProgressCallback(std::move(progressCallback)); + m_connectionManager->setFinishedCallback(std::move(finishedCallback)); + m_connectionManager->setCrashCallback(std::move(crashCallback)); + + m_model->setNodeInstanceView(m_nodeInstanceView); + + // InternalIds are not guaranteed to match between normal model and our copy of it, so + // we identify the View3D by its qml id. + m_nodeInstanceView->view3DAction(View3DActionType::SetBakeLightsView3D, m_view3dId); +} + +void BakeLights::cancel() +{ + if (!m_dialog.isNull() && m_dialog->isVisible()) + m_dialog->close(); + + deleteLater(); +} + +bool BakeLights::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_dialog) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) + cancel(); + } else if (event->type() == QEvent::Close) { + cancel(); + } + } + + return QObject::eventFilter(obj, event); +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/bakelights.h b/src/plugins/qmldesigner/components/edit3d/bakelights.h new file mode 100644 index 00000000000..d63ca0b3b82 --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/bakelights.h @@ -0,0 +1,54 @@ +// 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 +#include + +QT_BEGIN_NAMESPACE +class QQuickView; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class AbstractView; +class BakeLightsConnectionManager; +class NodeInstanceView; +class RewriterView; + +class BakeLights : public QObject +{ + Q_OBJECT + +public: + BakeLights(AbstractView *view); + ~BakeLights(); + + Q_INVOKABLE void cancel(); + + void raiseDialog(); + + static QString resolveView3dId(AbstractView *view); + +signals: + void finished(); + void progress(const QString &msg); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; + +private: + void bakeLights(); + + QPointer m_dialog; + QPointer m_connectionManager; + QPointer m_nodeInstanceView; + QPointer m_rewriterView; + QPointer m_view; + ModelPointer m_model; + QString m_view3dId; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/bakelightsconnectionmanager.cpp b/src/plugins/qmldesigner/components/edit3d/bakelightsconnectionmanager.cpp new file mode 100644 index 00000000000..201ca1d2e9b --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/bakelightsconnectionmanager.cpp @@ -0,0 +1,48 @@ +// 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 "bakelightsconnectionmanager.h" + +#include + +namespace QmlDesigner { + +BakeLightsConnectionManager::BakeLightsConnectionManager() +{ + connections().emplace_back("Bake lights", "bakelightsmode"); +} + +void BakeLightsConnectionManager::setProgressCallback(Callback callback) +{ + m_progressCallback = std::move(callback); +} + +void BakeLightsConnectionManager::setFinishedCallback(Callback callback) +{ + m_finishedCallback = std::move(callback); +} + +void BakeLightsConnectionManager::dispatchCommand(const QVariant &command, + ConnectionManagerInterface::Connection &) +{ + static const int commandType = QMetaType::type("PuppetToCreatorCommand"); + + if (command.userType() == commandType) { + auto cmd = command.value(); + switch (cmd.type()) { + case PuppetToCreatorCommand::BakeLightsProgress: + m_progressCallback(cmd.data().toString()); + break; + case PuppetToCreatorCommand::BakeLightsAborted: + m_finishedCallback(tr("Baking aborted!")); + break; + case PuppetToCreatorCommand::BakeLightsFinished: + m_finishedCallback(tr("Baking finished!")); + break; + default: + break; + } + } +} + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/bakelightsconnectionmanager.h b/src/plugins/qmldesigner/components/edit3d/bakelightsconnectionmanager.h new file mode 100644 index 00000000000..2af5e56c114 --- /dev/null +++ b/src/plugins/qmldesigner/components/edit3d/bakelightsconnectionmanager.h @@ -0,0 +1,28 @@ +// 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 "connectionmanager.h" + +namespace QmlDesigner { + +class BakeLightsConnectionManager : public ConnectionManager +{ +public: + using Callback = std::function; + + BakeLightsConnectionManager(); + + void setProgressCallback(Callback callback); + void setFinishedCallback(Callback callback); + +protected: + void dispatchCommand(const QVariant &command, Connection &connection) override; + +private: + Callback m_progressCallback; + Callback m_finishedCallback; +}; + +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp index 61731c578f8..12d2dd05528 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.cpp @@ -2,18 +2,15 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "edit3dactions.h" -#include "edit3dview.h" -#include -#include -#include -#include +#include "bakelights.h" +#include "edit3dview.h" +#include "nodemetainfo.h" +#include "qmldesignerconstants.h" #include "seekerslider.h" #include -#include - namespace QmlDesigner { Edit3DActionTemplate::Edit3DActionTemplate(const QString &description, @@ -147,4 +144,33 @@ bool Edit3DParticleSeekerAction::isEnabled(const SelectionContext &) const return m_seeker->isEnabled(); } +Edit3DBakeLightsAction::Edit3DBakeLightsAction(const QIcon &icon, + Edit3DView *view, + SelectionContextOperation selectionAction) + : Edit3DAction(QmlDesigner::Constants::EDIT3D_BAKE_LIGHTS, + View3DActionType::Empty, + QCoreApplication::translate("BakeLights", "Bake Lights"), + QKeySequence(), + false, + false, + icon, + view, + selectionAction, + QCoreApplication::translate("BakeLights", "Bake lights for the current 3D scene.")) + , m_view(view) +{ + +} + +bool Edit3DBakeLightsAction::isVisible(const SelectionContext &) const +{ + return m_view->isBakingLightsSupported(); +} + +bool Edit3DBakeLightsAction::isEnabled(const SelectionContext &) const +{ + return m_view->isBakingLightsSupported() + && !BakeLights::resolveView3dId(m_view).isEmpty(); +} + } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h index bad427a25be..4e8be202b9f 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dactions.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dactions.h @@ -122,4 +122,19 @@ private: SeekerSliderAction *m_seeker = nullptr; }; +class Edit3DBakeLightsAction : public Edit3DAction +{ +public: + Edit3DBakeLightsAction(const QIcon &icon, + Edit3DView *view, + SelectionContextOperation selectionAction); + +protected: + bool isVisible(const SelectionContext &) const override; + bool isEnabled(const SelectionContext &) const override; + +private: + Edit3DView *m_view = nullptr; +}; + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index d92e76362b9..d346e2bf164 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -4,6 +4,7 @@ #include "edit3dview.h" #include "backgroundcolorselection.h" +#include "bakelights.h" #include "designeractionmanager.h" #include "designericons.h" #include "designersettings.h" @@ -20,11 +21,16 @@ #include "qmldesignerplugin.h" #include "qmlvisualnode.h" #include "seekerslider.h" +#include "theme.h" #include #include -#include +#include +#include + +#include + #include #include #include @@ -204,6 +210,12 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) m_particlesPlayAction->action()->setChecked(sceneState[particlesPlayKey].toBool()); else m_particlesPlayAction->action()->setChecked(true); + + // Selection context change updates visible and enabled states + SelectionContext selectionContext(this); + selectionContext.setUpdateMode(SelectionContext::UpdateMode::Fast); + if (m_bakeLightsAction) + m_bakeLightsAction->currentContextChanged(selectionContext); } void Edit3DView::modelAttached(Model *model) @@ -219,6 +231,13 @@ void Edit3DView::modelAttached(Model *model) edit3DWidget()->canvas()->busyIndicator()->show(); + m_isBakingLightsSupported = false; + ProjectExplorer::Target *target = QmlDesignerPlugin::instance()->currentDesignDocument()->currentTarget(); + if (target && target->kit()) { + if (QtSupport::QtVersion *qtVer = QtSupport::QtKitAspect::qtVersion(target->kit())) + m_isBakingLightsSupported = qtVer->qtVersion() >= QVersionNumber(6, 5, 0); + } + connect(model->metaInfo().itemLibraryInfo(), &ItemLibraryInfo::entriesChanged, this, &Edit3DView::onEntriesChanged, Qt::UniqueConnection); } @@ -279,6 +298,11 @@ void Edit3DView::handleEntriesChanged() void Edit3DView::modelAboutToBeDetached(Model *model) { + m_isBakingLightsSupported = false; + + if (m_bakeLights) + m_bakeLights->cancel(); + // Hide the canvas when model is detached (i.e. changing documents) if (edit3DWidget() && edit3DWidget()->canvas()) { m_canvasCache.insert(model, edit3DWidget()->canvas()->renderImage()); @@ -702,6 +726,17 @@ void Edit3DView::createEdit3DActions() m_seekerAction->action()->setEnabled(!m_particlesPlayAction->action()->isChecked()); }; + SelectionContextOperation bakeLightsTrigger = [this](const SelectionContext &) { + if (!m_isBakingLightsSupported) + return; + + // BakeLights cleans itself up when its dialog is closed + if (!m_bakeLights) + m_bakeLights = new BakeLights(this); + else + m_bakeLights->raiseDialog(); + }; + m_particleViewModeAction = new Edit3DAction( QmlDesigner::Constants::EDIT3D_PARTICLE_MODE, View3DActionType::Edit3DParticleModeToggle, @@ -804,6 +839,11 @@ void Edit3DView::createEdit3DActions() m_seekerAction = createSeekerSliderAction(); + m_bakeLightsAction = new Edit3DBakeLightsAction( + toolbarIcon(Theme::editLightOn_medium), //: TODO placeholder icon + this, + bakeLightsTrigger); + m_leftActions << m_selectionModeAction; m_leftActions << nullptr; // Null indicates separator m_leftActions << nullptr; // Second null after separator indicates an exclusive group @@ -830,6 +870,7 @@ void Edit3DView::createEdit3DActions() m_rightActions << nullptr; m_rightActions << m_seekerAction; m_rightActions << nullptr; + m_rightActions << m_bakeLightsAction; m_rightActions << m_resetAction; m_visibilityToggleActions << m_showGridAction; @@ -870,6 +911,11 @@ Edit3DAction *Edit3DView::edit3DAction(View3DActionType type) const return m_edit3DActions.value(type, nullptr).data(); } +Edit3DBakeLightsAction *Edit3DView::bakeLightsAction() const +{ + return m_bakeLightsAction; +} + void Edit3DView::addQuick3DImport() { DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument(); @@ -939,4 +985,9 @@ void Edit3DView::dropAsset(const QString &file, const QPointF &pos) emitView3DAction(View3DActionType::GetNodeAtPos, pos); } +bool Edit3DView::isBakingLightsSupported() const +{ + return m_isBakingLightsSupported; +} + } // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 2b746cf1af7..109555f586b 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -22,8 +22,10 @@ QT_END_NAMESPACE namespace QmlDesigner { +class BakeLights; class Edit3DWidget; class Edit3DAction; +class Edit3DBakeLightsAction; class Edit3DCameraAction; class QMLDESIGNERCOMPONENTS_EXPORT Edit3DView : public AbstractView @@ -57,6 +59,7 @@ public: QVector visibilityToggleActions() const; QVector backgroundColorActions() const; Edit3DAction *edit3DAction(View3DActionType type) const; + Edit3DBakeLightsAction *bakeLightsAction() const; void addQuick3DImport(); void startContextMenu(const QPoint &pos); @@ -66,6 +69,8 @@ public: void dropComponent(const ItemLibraryEntry &entry, const QPointF &pos); void dropAsset(const QString &file, const QPointF &pos); + bool isBakingLightsSupported() const; + private slots: void onEntriesChanged(); @@ -122,6 +127,7 @@ private: Edit3DAction *m_visibilityTogglesAction = nullptr; Edit3DAction *m_backgrondColorMenuAction = nullptr; Edit3DAction *m_seekerAction = nullptr; + Edit3DBakeLightsAction *m_bakeLightsAction = nullptr; int particlemode; ModelCache m_canvasCache; ModelNode m_droppedModelNode; @@ -130,6 +136,8 @@ private: NodeAtPosReqType m_nodeAtPosReqType; QPoint m_contextMenuPos; QTimer m_compressionTimer; + QPointer m_bakeLights; + bool m_isBakingLightsSupported = false; friend class Edit3DAction; }; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 408584e166b..a217e396d9c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -261,6 +261,12 @@ void Edit3DWidget::createContextMenu() view()->emitView3DAction(View3DActionType::AlignViewToCamera, true); }); + m_bakeLightsAction = m_contextMenu->addAction( + contextIcon(DesignerIcons::LightIcon), // TODO: placeholder icon + tr("Bake Lights"), [&] { + view()->bakeLightsAction()->action()->trigger(); + }); + m_contextMenu->addSeparator(); m_selectParentAction = m_contextMenu->addAction( @@ -466,6 +472,8 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode m_alignViewAction->setEnabled(isCamera); m_selectParentAction->setEnabled(selectionExcludingRoot); m_toggleGroupAction->setEnabled(true); + m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible()); + m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled()); m_contextMenu->popup(mapToGlobal(pos)); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index 092abd313a5..eecd52345fa 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -75,6 +75,7 @@ private: QPointer m_visibilityTogglesMenu; QPointer m_backgroundColorMenu; QPointer m_contextMenu; + QPointer m_bakeLightsAction; QPointer m_editComponentAction; QPointer m_editMaterialAction; QPointer m_duplicateAction; diff --git a/src/plugins/qmldesigner/designercore/include/model.h b/src/plugins/qmldesigner/designercore/include/model.h index 9e6c7ca36ad..f3227361e68 100644 --- a/src/plugins/qmldesigner/designercore/include/model.h +++ b/src/plugins/qmldesigner/designercore/include/model.h @@ -87,6 +87,7 @@ public: NodeMetaInfo flowViewFlowTransitionMetaInfo() const; NodeMetaInfo flowViewFlowWildcardMetaInfo() const; NodeMetaInfo fontMetaInfo() const; + NodeMetaInfo qtQuick3DBakedLightmapMetaInfo() const; NodeMetaInfo qtQuick3DDefaultMaterialMetaInfo() const; NodeMetaInfo qtQuick3DMaterialMetaInfo() const; NodeMetaInfo qtQuick3DModelMetaInfo() const; diff --git a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h index 467cef0ed3d..11017a27b1c 100644 --- a/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h +++ b/src/plugins/qmldesigner/designercore/include/nodeinstanceview.h @@ -122,6 +122,7 @@ public: QImage statePreviewImage(const ModelNode &stateNode) const; void setTarget(ProjectExplorer::Target *newTarget); + ProjectExplorer::Target *target() const; void sendToken(const QString &token, int number, const QVector &nodeVector); diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 7aaed70c9dd..ebe5acd9d28 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -1572,6 +1572,11 @@ void NodeInstanceView::setTarget(ProjectExplorer::Target *newTarget) } } +ProjectExplorer::Target *NodeInstanceView::target() const +{ + return m_currentTarget; +} + void NodeInstanceView::statePreviewImagesChanged(const StatePreviewImageChangedCommand &command) { if (!model()) diff --git a/src/plugins/qmldesigner/designercore/model/model.cpp b/src/plugins/qmldesigner/designercore/model/model.cpp index a234c11e1fe..13197666fc4 100644 --- a/src/plugins/qmldesigner/designercore/model/model.cpp +++ b/src/plugins/qmldesigner/designercore/model/model.cpp @@ -1939,6 +1939,16 @@ NodeMetaInfo Model::qtQuick3DTextureMetaInfo() const } } +NodeMetaInfo Model::qtQuick3DBakedLightmapMetaInfo() const +{ + if constexpr (useProjectStorage()) { + using namespace Storage::Info; + return createNodeMetaInfo(); + } else { + return metaInfo("QtQuick3D.BakedLightmap"); + } +} + NodeMetaInfo Model::qtQuick3DMaterialMetaInfo() const { if constexpr (useProjectStorage()) { diff --git a/src/plugins/qmldesigner/qmldesignerconstants.h b/src/plugins/qmldesigner/qmldesignerconstants.h index 743d70bcbc9..d38dad74c7e 100644 --- a/src/plugins/qmldesigner/qmldesignerconstants.h +++ b/src/plugins/qmldesigner/qmldesignerconstants.h @@ -62,7 +62,7 @@ const char EDIT3D_PARTICLES_SEEKER[] = "QmlDesigner.Editor3D.ParticlesSeeker" const char EDIT3D_PARTICLES_RESTART[] = "QmlDesigner.Editor3D.ParticlesRestart"; const char EDIT3D_VISIBILITY_TOGGLES[] = "QmlDesigner.Editor3D.VisibilityToggles"; const char EDIT3D_BACKGROUND_COLOR_ACTIONS[] = "QmlDesigner.Editor3D.BackgroundColorActions"; - +const char EDIT3D_BAKE_LIGHTS[] = "QmlDesigner.Editor3D.BakeLights"; const char QML_DESIGNER_SUBFOLDER[] = "/designer/"; const char COMPONENT_BUNDLES_FOLDER[] = "/ComponentBundles"; diff --git a/src/plugins/qmldesigner/settingspage.cpp b/src/plugins/qmldesigner/settingspage.cpp index 6acb2284217..52a58e01a5e 100644 --- a/src/plugins/qmldesigner/settingspage.cpp +++ b/src/plugins/qmldesigner/settingspage.cpp @@ -41,7 +41,7 @@ namespace Internal { static QStringList puppetModes() { - static QStringList puppetModeList{"", "all", "editormode", "rendermode", "previewmode"}; + static QStringList puppetModeList{"", "all", "editormode", "rendermode", "previewmode", "bakelightsmode"}; return puppetModeList; } diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt index 62325819a58..1d8dc8691b4 100644 --- a/src/tools/qml2puppet/CMakeLists.txt +++ b/src/tools/qml2puppet/CMakeLists.txt @@ -175,6 +175,7 @@ extend_qtc_executable(qml2puppet qmlstatenodeinstance.cpp qmlstatenodeinstance.h qmltransitionnodeinstance.cpp qmltransitionnodeinstance.h qt3dpresentationnodeinstance.cpp qt3dpresentationnodeinstance.h + qt5bakelightsnodeinstanceserver.cpp qt5bakelightsnodeinstanceserver.h qt5informationnodeinstanceserver.cpp qt5informationnodeinstanceserver.h qt5nodeinstanceclientproxy.cpp qt5nodeinstanceclientproxy.h qt5nodeinstanceserver.cpp qt5nodeinstanceserver.h diff --git a/src/tools/qml2puppet/qml2puppet/instances/instances.pri b/src/tools/qml2puppet/qml2puppet/instances/instances.pri index 2d3b4ceedfe..505dd748e49 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/instances.pri +++ b/src/tools/qml2puppet/qml2puppet/instances/instances.pri @@ -25,6 +25,7 @@ HEADERS += $$PWD/qt5nodeinstanceserver.h \ $$PWD/qt5captureimagenodeinstanceserver.h \ $$PWD/qt5capturepreviewnodeinstanceserver.h \ $$PWD/qt5testnodeinstanceserver.h \ + $$PWD/qt5bakelightsnodeinstanceserver.h \ $$PWD/qt5informationnodeinstanceserver.h \ $$PWD/qt5rendernodeinstanceserver.h \ $$PWD/qt5previewnodeinstanceserver.h \ @@ -60,6 +61,7 @@ SOURCES += $$PWD/qt5nodeinstanceserver.cpp \ $$PWD/qt5captureimagenodeinstanceserver.cpp \ $$PWD/qt5capturepreviewnodeinstanceserver.cpp \ $$PWD/qt5testnodeinstanceserver.cpp \ + $$PWD/qt5bakelightsnodeinstanceserver.cpp \ $$PWD/qt5informationnodeinstanceserver.cpp \ $$PWD/qt5rendernodeinstanceserver.cpp \ $$PWD/qt5previewnodeinstanceserver.cpp \ diff --git a/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp b/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp index 09910847a94..9815d21ac47 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/nodeinstanceserverdispatcher.cpp @@ -3,6 +3,7 @@ #include "nodeinstanceserverdispatcher.h" +#include "qt5bakelightsnodeinstanceserver.h" #include "qt5captureimagenodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" @@ -170,6 +171,8 @@ std::unique_ptr createNodeInstanceServer( return std::make_unique(nodeInstanceClient); else if (serverName == "previewmode") return std::make_unique(nodeInstanceClient); + else if (serverName == "bakelightsmode") + return std::make_unique(nodeInstanceClient); return {}; } diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5bakelightsnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5bakelightsnodeinstanceserver.cpp new file mode 100644 index 00000000000..87b125480d8 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5bakelightsnodeinstanceserver.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#include "qt5bakelightsnodeinstanceserver.h" + +#if BAKE_LIGHTS_SUPPORTED +#include "createscenecommand.h" +#include "view3dactioncommand.h" + +#include "nodeinstanceclientinterface.h" +#include "puppettocreatorcommand.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#endif + +namespace QmlDesigner { + +Qt5BakeLightsNodeInstanceServer::Qt5BakeLightsNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient) : + Qt5NodeInstanceServer(nodeInstanceClient) +{ + setSlowRenderTimerInterval(100000000); + setRenderTimerInterval(100); +} + +#if BAKE_LIGHTS_SUPPORTED + +Qt5BakeLightsNodeInstanceServer::~Qt5BakeLightsNodeInstanceServer() +{ + cleanup(); +} + +void Qt5BakeLightsNodeInstanceServer::createScene(const CreateSceneCommand &command) +{ + initializeView(); + registerFonts(command.resourceUrl); + setTranslationLanguage(command.language); + setupScene(command); + startRenderTimer(); + + // Set working directory to a temporary directory, as baking process creates a file there + if (m_workingDir.isValid()) + QDir::setCurrent(m_workingDir.path()); +} + +void Qt5BakeLightsNodeInstanceServer::view3DAction(const View3DActionCommand &command) +{ + switch (command.type()) { + case View3DActionType::SetBakeLightsView3D: { + QString id = command.value().toString(); + const QList allViews = allView3DInstances(); + for (const auto &view : allViews) { + if (view.id() == id) { + m_view3D = qobject_cast(view.internalObject()); + break; + } + } + + if (!m_view3D) + abort(tr("View3D not found: '%1'").arg(id)); + else + startRenderTimer(); + break; + } + default: + break; + } +} + +void Qt5BakeLightsNodeInstanceServer::startRenderTimer() +{ + if (timerId() != 0) + killTimer(timerId()); + + int timerId = startTimer(renderTimerInterval()); + + setTimerId(timerId); +} + +void Qt5BakeLightsNodeInstanceServer::bakeLights() +{ + if (!m_view3D) { + abort(tr("Invalid View3D object set.")); + return; + } + + QQuick3DLightmapBaker::Callback callback = [this](QQuick3DLightmapBaker::BakingStatus status, + std::optional msg, QQuick3DLightmapBaker::BakingControl *) { + switch (status) { + case QQuick3DLightmapBaker::BakingStatus::Progress: + case QQuick3DLightmapBaker::BakingStatus::Warning: + case QQuick3DLightmapBaker::BakingStatus::Error: + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsProgress, msg.value_or("")}); + break; + case QQuick3DLightmapBaker::BakingStatus::Cancelled: + abort(tr("Baking cancelled.")); + break; + case QQuick3DLightmapBaker::BakingStatus::Complete: + runDenoiser(); + break; + default: + qWarning() << __FUNCTION__ << "Unexpected light baking status received:" + << int(status) << msg.value_or(""); + break; + } + }; + + QQuick3DLightmapBaker *baker = m_view3D->lightmapBaker(); + baker->bake(callback); + + m_bakingStarted = true; +} + +void Qt5BakeLightsNodeInstanceServer::cleanup() +{ + m_workingDir.remove(); + if (m_denoiser) { + if (m_denoiser->state() == QProcess::Running) + m_denoiser->terminate(); + m_denoiser->deleteLater(); + } +} + +void Qt5BakeLightsNodeInstanceServer::abort(const QString &msg) +{ + cleanup(); + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsAborted, msg}); +} + +void Qt5BakeLightsNodeInstanceServer::finish() +{ + cleanup(); + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsFinished, {}}); +} + +void Qt5BakeLightsNodeInstanceServer::runDenoiser() +{ + // Check if denoiser exists (as it is third party app) + QString binPath = QLibraryInfo::path(QLibraryInfo::BinariesPath); +#if defined(Q_OS_WIN) + binPath += "/qlmdenoiser.exe"; +#elif defined(Q_OS_MACOS) + // TODO: What is the path in mac? +#else + binPath += "/qlmdenoiser"; +#endif + + QFileInfo fi(binPath); + if (!fi.exists()) { + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsProgress, + tr("Warning: Denoiser executable not found, cannot denoise baked lightmaps (%1).") + .arg(binPath)}); + finish(); + return; + } + + m_denoiser = new QProcess(); + + QObject::connect(m_denoiser, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { + m_workingDir.remove(); + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsProgress, + tr("Warning: An error occurred while running denoiser process!")}); + finish(); + }); + + QObject::connect(m_denoiser, &QProcess::finished, this, [this](int exitCode, + QProcess::ExitStatus exitStatus) { + if (exitCode == 0 && exitStatus == QProcess::NormalExit) { + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsProgress, + tr("Denoising finished.")}); + } else { + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsProgress, + tr("Warning: Denoiser process failed with exit code '%1'!").arg(exitCode)}); + } + finish(); + }); + + nodeInstanceClient()->handlePuppetToCreatorCommand( + {PuppetToCreatorCommand::BakeLightsProgress, + tr("Denoising baked lightmaps...")}); + + m_denoiser->setWorkingDirectory(m_workingDir.path()); + m_denoiser->start(binPath, {"qlm_list.txt"}); + +} + +void Qt5BakeLightsNodeInstanceServer::collectItemChangesAndSendChangeCommands() +{ + static bool inFunction = false; + + if (!rootNodeInstance().holdsGraphical()) + return; + + if (!inFunction) { + inFunction = true; + + QQuickDesignerSupport::polishItems(quickWindow()); + + render(); + + inFunction = false; + } +} + +void Qt5BakeLightsNodeInstanceServer::render() +{ + // Render multiple times to make sure everything gets rendered correctly before baking + if (++m_renderCount == 4) { + bakeLights(); + } else { + rootNodeInstance().updateDirtyNodeRecursive(); + renderWindow(); + if (m_bakingStarted) + slowDownRenderTimer(); // No more renders needed + } +} +#endif + +} // namespace QmlDesigner diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5bakelightsnodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5bakelightsnodeinstanceserver.h new file mode 100644 index 00000000000..fc99adc9021 --- /dev/null +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5bakelightsnodeinstanceserver.h @@ -0,0 +1,57 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0 WITH Qt-GPL-exception-1.0 + +#pragma once + +#include "qt5nodeinstanceserver.h" + +#if QUICK3D_MODULE && QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) +#include +#define BAKE_LIGHTS_SUPPORTED 1 +#endif + +QT_BEGIN_NAMESPACE +class QProcess; +class QQuick3DViewport; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class Qt5BakeLightsNodeInstanceServer : public Qt5NodeInstanceServer +{ + Q_OBJECT +public: + explicit Qt5BakeLightsNodeInstanceServer(NodeInstanceClientInterface *nodeInstanceClient); + +#if BAKE_LIGHTS_SUPPORTED +public: + virtual ~Qt5BakeLightsNodeInstanceServer(); + + void createScene(const CreateSceneCommand &command) override; + void view3DAction(const View3DActionCommand &command) override; + + void render(); + +protected: + void collectItemChangesAndSendChangeCommands() override; + void startRenderTimer() override; + void bakeLights(); + +private: + void abort(const QString &msg); + void runDenoiser(); + void finish(); + void cleanup(); + + QQuick3DViewport *m_view3D = nullptr; + bool m_bakingStarted = false; + int m_renderCount = 0; + QProcess *m_denoiser = nullptr; + QTemporaryDir m_workingDir; +#else +protected: + void collectItemChangesAndSendChangeCommands() override {}; +#endif +}; + +} // namespace QmlDesigner diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp index c5c6f6dc965..b0726b152a0 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5nodeinstanceclientproxy.cpp @@ -6,6 +6,7 @@ #include #include "capturenodeinstanceserverdispatcher.h" +#include "qt5bakelightsnodeinstanceserver.h" #include "qt5captureimagenodeinstanceserver.h" #include "qt5capturepreviewnodeinstanceserver.h" #include "qt5informationnodeinstanceserver.h" @@ -74,6 +75,9 @@ Qt5NodeInstanceClientProxy::Qt5NodeInstanceClientProxy(QObject *parent) : } else if (QCoreApplication::arguments().at(2) == QLatin1String("captureiconmode")) { setNodeInstanceServer(std::make_unique(this)); initializeSocket(); + } else if (QCoreApplication::arguments().at(2) == QLatin1String("bakelightsmode")) { + setNodeInstanceServer(std::make_unique(this)); + initializeSocket(); } } diff --git a/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.h b/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.h index b8e45491a8a..abb16d74bdf 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.h +++ b/src/tools/qml2puppet/qml2puppet/instances/servernodeinstance.h @@ -57,6 +57,7 @@ using QHashValueType = size_t; friend class Qt4PreviewNodeInstanceServer; friend class Qt5InformationNodeInstanceServer; friend class Qt5NodeInstanceServer; + friend class Qt5BakeLightsNodeInstanceServer; friend class Qt5PreviewNodeInstanceServer; friend class Qt5CapturePreviewNodeInstanceServer; friend class Qt5TestNodeInstanceServer;