QmlDesigner: Implement basic lights baking support

It is expected that user manually specifies all necessary light baking
related properties, including exposing them from subcomponents if
needed.

qlmdenoiser that is used to denoise generated light maps is a third
party application. It can be found here:
https://git.qt.io/laagocs/qlmdenoiser

Fixes: QDS-9403
Change-Id: Ida6fc142440b9ffa8cc97d578f85d8b76cb4b43f
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
Miikka Heikkinen
2023-03-16 14:31:41 +02:00
committed by Mahmoud Badri
parent 2175b976fb
commit e82898a184
27 changed files with 916 additions and 12 deletions

View File

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

View File

@@ -19,7 +19,11 @@ public:
RenderModelNodePreviewImage,
Import3DSupport,
NodeAtPos,
None };
BakeLightsProgress,
BakeLightsFinished,
BakeLightsAborted,
None
};
PuppetToCreatorCommand(Type type, const QVariant &data);
PuppetToCreatorCommand() = default;

View File

@@ -48,7 +48,8 @@ enum class View3DActionType {
SelectGridColor,
ResetBackgroundColor,
SyncBackgroundColor,
GetNodeAtPos
GetNodeAtPos,
SetBakeLightsView3D
};
constexpr bool isNanotraceEnabled()

View File

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

View File

@@ -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 <utils/algorithm.h>
#include <utils/environment.h>
#include <utils/filepath.h>
#include <utils/qtcassert.h>
#include <coreplugin/icore.h>
#include <QEvent>
#include <QQmlContext>
#include <QQmlEngine>
#include <QQuickView>
#include <QTextCursor>
#include <QTextDocument>
#include <QTimer>
#include <QVariant>
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<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);
}
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<QTextDocument>(
m_view->model()->rewriterView()->textModifier()->textDocument()->toRawText());
auto modifier = std::make_unique<NotIndentingTextEditModifier>(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<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Escape)
cancel();
} else if (event->type() == QEvent::Close) {
cancel();
}
}
return QObject::eventFilter(obj, event);
}
} // namespace QmlDesigner

View File

@@ -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 <QObject>
#include <QPointer>
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<QQuickView> m_dialog;
QPointer<BakeLightsConnectionManager> m_connectionManager;
QPointer<NodeInstanceView> m_nodeInstanceView;
QPointer<RewriterView> m_rewriterView;
QPointer<AbstractView> m_view;
ModelPointer m_model;
QString m_view3dId;
};
} // namespace QmlDesigner

View File

@@ -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 <puppettocreatorcommand.h>
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<PuppetToCreatorCommand>();
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

View File

@@ -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<void(const QString &)>;
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

View File

@@ -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 <viewmanager.h>
#include <nodeinstanceview.h>
#include <nodemetainfo.h>
#include <qmldesignerplugin.h>
#include "bakelights.h"
#include "edit3dview.h"
#include "nodemetainfo.h"
#include "qmldesignerconstants.h"
#include "seekerslider.h"
#include <utils/algorithm.h>
#include <QDebug>
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();
}
}

View File

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

View File

@@ -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 <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
#include <theme.h>
#include <projectexplorer/target.h>
#include <projectexplorer/kit.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
@@ -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

View File

@@ -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<Edit3DAction *> visibilityToggleActions() const;
QVector<Edit3DAction *> 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<QImage> m_canvasCache;
ModelNode m_droppedModelNode;
@@ -130,6 +136,8 @@ private:
NodeAtPosReqType m_nodeAtPosReqType;
QPoint m_contextMenuPos;
QTimer m_compressionTimer;
QPointer<BakeLights> m_bakeLights;
bool m_isBakingLightsSupported = false;
friend class Edit3DAction;
};

View File

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

View File

@@ -75,6 +75,7 @@ private:
QPointer<QMenu> m_visibilityTogglesMenu;
QPointer<QMenu> m_backgroundColorMenu;
QPointer<QMenu> m_contextMenu;
QPointer<QAction> m_bakeLightsAction;
QPointer<QAction> m_editComponentAction;
QPointer<QAction> m_editMaterialAction;
QPointer<QAction> m_duplicateAction;

View File

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

View File

@@ -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<ModelNode> &nodeVector);

View File

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

View File

@@ -1939,6 +1939,16 @@ NodeMetaInfo Model::qtQuick3DTextureMetaInfo() const
}
}
NodeMetaInfo Model::qtQuick3DBakedLightmapMetaInfo() const
{
if constexpr (useProjectStorage()) {
using namespace Storage::Info;
return createNodeMetaInfo<QtQuick3D, BakedLightmap>();
} else {
return metaInfo("QtQuick3D.BakedLightmap");
}
}
NodeMetaInfo Model::qtQuick3DMaterialMetaInfo() const
{
if constexpr (useProjectStorage()) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<NodeInstanceServer> createNodeInstanceServer(
return std::make_unique<Qt5InformationNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "previewmode")
return std::make_unique<Qt5PreviewNodeInstanceServer>(nodeInstanceClient);
else if (serverName == "bakelightsmode")
return std::make_unique<Qt5BakeLightsNodeInstanceServer>(nodeInstanceClient);
return {};
}

View File

@@ -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 <QDir>
#include <QFileInfo>
#include <QLibraryInfo>
#include <QProcess>
#include <QQuickView>
#include <QQuickItem>
#include <private/qquickdesignersupport_p.h>
#include <private/qquick3dviewport_p.h>
#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<ServerNodeInstance> allViews = allView3DInstances();
for (const auto &view : allViews) {
if (view.id() == id) {
m_view3D = qobject_cast<QQuick3DViewport *>(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<QString> 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

View File

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

View File

@@ -6,6 +6,7 @@
#include <QCoreApplication>
#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<Qt5CaptureImageNodeInstanceServer>(this));
initializeSocket();
} else if (QCoreApplication::arguments().at(2) == QLatin1String("bakelightsmode")) {
setNodeInstanceServer(std::make_unique<Qt5BakeLightsNodeInstanceServer>(this));
initializeSocket();
}
}

View File

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