diff --git a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h index 30b0cce9981..1e8d82e632b 100644 --- a/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h +++ b/src/libs/qmlpuppetcommunication/commands/puppettocreatorcommand.h @@ -16,6 +16,7 @@ public: Edit3DToolState, Render3DView, ActiveSceneChanged, + ActiveSplitChanged, RenderModelNodePreviewImage, Import3DSupport, NodeAtPos, diff --git a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h index 791b285e66f..aefaeb88e3f 100644 --- a/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h +++ b/src/libs/qmlpuppetcommunication/interfaces/nodeinstanceglobal.h @@ -47,7 +47,9 @@ enum class View3DActionType { SyncEnvBackground, GetNodeAtPos, SetBakeLightsView3D, - SplitViewToggle + SplitViewToggle, + MaterialOverride, + ShowWireframe }; constexpr bool isNanotraceEnabled() diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp index 319a4d510a2..5c1b0d39a3c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.cpp @@ -58,6 +58,9 @@ Edit3DView::Edit3DView(ExternalDependenciesInterface &externalDependencies) m_compressionTimer.setInterval(1000); m_compressionTimer.setSingleShot(true); connect(&m_compressionTimer, &QTimer::timeout, this, &Edit3DView::handleEntriesChanged); + + for (int i = 0; i < 4; ++i) + m_splitToolStates.append({0, false}); } void Edit3DView::createEdit3DWidget() @@ -108,6 +111,18 @@ void Edit3DView::renderImage3DChanged(const QImage &img) void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) { + const QString activeSplitKey = QStringLiteral("activeSplit"); + if (sceneState.contains(activeSplitKey)) { + m_activeSplit = sceneState[activeSplitKey].toInt(); + + // If the sceneState contained just activeSplit key, then this is simply an active split + // change rather than entire active scene change, and we don't need to process further. + if (sceneState.size() == 1) + return; + } else { + m_activeSplit = 0; + } + const QString sceneKey = QStringLiteral("sceneInstanceId"); const QString selectKey = QStringLiteral("selectionMode"); const QString transformKey = QStringLiteral("transformMode"); @@ -122,6 +137,8 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) const QString particlesPlayKey = QStringLiteral("particlePlay"); const QString syncEnvBgKey = QStringLiteral("syncEnvBackground"); const QString splitViewKey = QStringLiteral("splitView"); + const QString matOverrideKey = QStringLiteral("matOverride"); + const QString showWireframeKey = QStringLiteral("showWireframe"); if (sceneState.contains(sceneKey)) { qint32 newActiveScene = sceneState[sceneKey].value(); @@ -197,6 +214,24 @@ void Edit3DView::updateActiveScene3D(const QVariantMap &sceneState) else m_splitViewAction->action()->setChecked(false); + if (sceneState.contains(matOverrideKey)) { + const QVariantList overrides = sceneState[matOverrideKey].toList(); + for (int i = 0; i < 4; ++i) + m_splitToolStates[i].matOverride = i < overrides.size() ? overrides[i].toInt() : 0; + } else { + for (SplitToolState &state : m_splitToolStates) + state.matOverride = 0; + } + + if (sceneState.contains(showWireframeKey)) { + const QVariantList showList = sceneState[showWireframeKey].toList(); + for (int i = 0; i < 4; ++i) + m_splitToolStates[i].showWireframe = i < showList.size() ? showList[i].toBool() : false; + } else { + for (SplitToolState &state : m_splitToolStates) + state.showWireframe = false; + } + // Syncing background color only makes sense for children of View3D instances bool syncValue = false; bool syncEnabled = false; @@ -663,6 +698,24 @@ void Edit3DView::syncSnapAuxPropsToSettings() Edit3DViewConfig::load(DesignerSettingsKey::EDIT3DVIEW_SNAP_SCALE_INTERVAL)); } +const QList &Edit3DView::splitToolStates() const +{ + return m_splitToolStates; +} + +void Edit3DView::setSplitToolState(int splitIndex, const SplitToolState &state) +{ + if (splitIndex >= m_splitToolStates.size()) + return; + + m_splitToolStates[splitIndex] = state; +} + +int Edit3DView::activeSplit() const +{ + return m_activeSplit; +} + void Edit3DView::createEdit3DActions() { m_selectionModeAction = std::make_unique( diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dview.h b/src/plugins/qmldesigner/components/edit3d/edit3dview.h index 84742943a22..56248d9587c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dview.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dview.h @@ -32,6 +32,12 @@ class QMLDESIGNERCOMPONENTS_EXPORT Edit3DView : public AbstractView Q_OBJECT public: + struct SplitToolState + { + int matOverride = 0; + bool showWireframe = false; + }; + Edit3DView(ExternalDependenciesInterface &externalDependencies); WidgetInfo widgetInfo() override; @@ -78,6 +84,11 @@ public: void syncSnapAuxPropsToSettings(); + const QList &splitToolStates() const; + void setSplitToolState(int splitIndex, const SplitToolState &state); + + int activeSplit() const; + private slots: void onEntriesChanged(); @@ -160,6 +171,9 @@ private: QPointer m_bakeLights; bool m_isBakingLightsSupported = false; QPointer m_snapConfiguration; + int m_activeSplit = 0; + + QList m_splitToolStates; friend class Edit3DAction; }; diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp index 70de2d29367..112d4e7eea5 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.cpp @@ -281,6 +281,95 @@ void Edit3DWidget::createContextMenu() m_toggleGroupAction->setChecked(defaultToggleGroupAction->isChecked()); m_contextMenu->addSeparator(); + + auto overridesSubMenu = new QmlEditorMenu(tr("Shader Overrides"), m_contextMenu); + overridesSubMenu->setToolTipsVisible(true); + m_contextMenu->addMenu(overridesSubMenu); + + m_wireFrameAction = overridesSubMenu->addAction( + tr("Wireframe"), this, &Edit3DWidget::onWireframeAction); + m_wireFrameAction->setCheckable(true); + m_wireFrameAction->setToolTip(tr("Show models as wireframe.")); + + overridesSubMenu->addSeparator(); + + // The type enum order must match QQuick3DDebugSettings::QQuick3DMaterialOverrides enums + enum class MaterialOverrideType { + None, + BaseColor, + Roughness, + Metalness, + Diffuse, + Specular, + ShadowOcclusion, + Emission, + AmbientOcclusion, + Normals, + Tangents, + Binormals, + F0 + }; + + auto addOverrideMenuAction = [&](const QString &label, const QString &toolTip, + MaterialOverrideType type) { + QAction *action = overridesSubMenu->addAction( + label, this, &Edit3DWidget::onMatOverrideAction); + action->setData(int(type)); + action->setCheckable(true); + action->setToolTip(toolTip); + m_matOverrideActions.insert(int(type), action); + }; + + addOverrideMenuAction(tr("No Material Override"), + tr("Rendering occurs as normal."), + MaterialOverrideType::None); + addOverrideMenuAction(tr("Base Color"), + tr("The BaseColor or Diffuse color of a material is passed through without any lighting."), + MaterialOverrideType::BaseColor); + addOverrideMenuAction(tr("Roughness"), + tr("The Roughness of a material is passed through as an unlit greyscale value."), + MaterialOverrideType::Roughness); + addOverrideMenuAction(tr("Metalness"), + tr("The Metalness of a material is passed through as an unlit greyscale value."), + MaterialOverrideType::Metalness); + addOverrideMenuAction(tr("Diffuse"), + tr("Only the diffuse contribution of the material after all lighting."), + MaterialOverrideType::Diffuse); + addOverrideMenuAction(tr("Specular"), + tr("Only the specular contribution of the material after all lighting."), + MaterialOverrideType::Specular); + addOverrideMenuAction(tr("Shadow Occlusion"), + tr("The Occlusion caused by shadows as a greyscale value."), + MaterialOverrideType::ShadowOcclusion); + addOverrideMenuAction(tr("Emission"), + tr("Only the emissive contribution of the material."), + MaterialOverrideType::Emission); + addOverrideMenuAction(tr("Ambient Occlusion"), + tr("Only the Ambient Occlusion of the material."), + MaterialOverrideType::AmbientOcclusion); + addOverrideMenuAction(tr("Normals"), + tr("The interpolated world space Normal value of the material mapped to an RGB color."), + MaterialOverrideType::Normals); + addOverrideMenuAction(tr("Tangents"), + tr("The interpolated world space Tangent value of the material mapped to an RGB color.\n" + "This will only be visible if the Tangent value is used."), + MaterialOverrideType::Tangents); + addOverrideMenuAction(tr("Binormals"), + tr("The interpolated world space Binormal value of the material mapped to an RGB color.\n" + "This will only be visible if the Binormal value is used."), + MaterialOverrideType::Binormals); + addOverrideMenuAction(tr("Fresnel 0"), + tr("This represents the Fresnel Reflectance at 0 Degrees.\n" + "This will only be visible for materials that calculate an F0 value."), + MaterialOverrideType::F0); + + overridesSubMenu->addSeparator(); + + QAction *resetAction = overridesSubMenu->addAction( + tr("Reset All Overrides"), this, &Edit3DWidget::onResetAllOverridesAction); + resetAction->setToolTip(tr("Reset all overrides for all splits.")); + + m_contextMenu->addSeparator(); } bool Edit3DWidget::isPasteAvailable() const @@ -422,6 +511,69 @@ void Edit3DWidget::onCreateAction() }); } +void Edit3DWidget::onMatOverrideAction() +{ + QAction *action = qobject_cast(sender()); + if (!action || !m_view || !m_view->model()) + return; + + QVariantList list; + for (int i = 0; i < m_view->splitToolStates().size(); ++i) { + Edit3DView::SplitToolState state = m_view->splitToolStates()[i]; + if (i == m_view->activeSplit()) { + state.matOverride = action->data().toInt(); + m_view->setSplitToolState(i, state); + list.append(action->data()); + } else { + list.append(state.matOverride); + } + } + + view()->emitView3DAction(View3DActionType::MaterialOverride, list); +} + +void Edit3DWidget::onWireframeAction() +{ + QAction *action = qobject_cast(sender()); + if (!action || !m_view || !m_view->model()) + return; + + QVariantList list; + for (int i = 0; i < m_view->splitToolStates().size(); ++i) { + Edit3DView::SplitToolState state = m_view->splitToolStates()[i]; + if (i == m_view->activeSplit()) { + state.showWireframe = action->isChecked(); + m_view->setSplitToolState(i, state); + list.append(action->isChecked()); + } else { + list.append(state.showWireframe); + } + } + + view()->emitView3DAction(View3DActionType::ShowWireframe, list); +} + +void Edit3DWidget::onResetAllOverridesAction() +{ + if (!m_view || !m_view->model()) + return; + + QVariantList wList; + QVariantList mList; + + for (int i = 0; i < m_view->splitToolStates().size(); ++i) { + Edit3DView::SplitToolState state; + state.showWireframe = false; + state.matOverride = 0; + m_view->setSplitToolState(i, state); + wList.append(state.showWireframe); + mList.append(state.matOverride); + } + + view()->emitView3DAction(View3DActionType::ShowWireframe, wList); + view()->emitView3DAction(View3DActionType::MaterialOverride, mList); +} + void Edit3DWidget::contextHelp(const Core::IContext::HelpCallback &callback) const { if (m_view) @@ -502,6 +654,15 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible()); m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled()); + if (m_view) { + int idx = m_view->activeSplit(); + m_wireFrameAction->setChecked(m_view->splitToolStates()[idx].showWireframe); + for (QAction *a : std::as_const(m_matOverrideActions)) + a->setChecked(false); + int type = m_view->splitToolStates()[idx].matOverride; + m_matOverrideActions[type]->setChecked(true); + } + m_contextMenu->popup(mapToGlobal(pos)); } diff --git a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h index 7f119c205a5..67e23b0b98c 100644 --- a/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h +++ b/src/plugins/qmldesigner/components/edit3d/edit3dwidget.h @@ -54,6 +54,9 @@ public: private slots: void onCreateAction(); + void onMatOverrideAction(); + void onWireframeAction(); + void onResetAllOverridesAction(); protected: void dragEnterEvent(QDragEnterEvent *dragEnterEvent) override; @@ -90,6 +93,8 @@ private: QPointer m_alignViewAction; QPointer m_selectParentAction; QPointer m_toggleGroupAction; + QPointer m_wireFrameAction; + QHash> m_matOverrideActions; QPointer m_createSubMenu; ModelNode m_contextMenuTarget; QVector3D m_contextMenuPos3d; diff --git a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp index 2038e7f3cee..cd56af86643 100644 --- a/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp +++ b/src/plugins/qmldesigner/designercore/instances/nodeinstanceview.cpp @@ -1719,6 +1719,11 @@ void NodeInstanceView::handlePuppetToCreatorCommand(const PuppetToCreatorCommand } else if (command.type() == PuppetToCreatorCommand::ActiveSceneChanged) { const auto sceneState = qvariant_cast(command.data()); emitUpdateActiveScene3D(sceneState); + } else if (command.type() == PuppetToCreatorCommand::ActiveSplitChanged) { + // Active split change is a special case of active scene change + QVariantMap splitState; + splitState.insert("activeSplit", command.data()); + emitUpdateActiveScene3D(splitState); } else if (command.type() == PuppetToCreatorCommand::RenderModelNodePreviewImage) { ImageContainer container = qvariant_cast(command.data()); QImage image = container.image(); diff --git a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml index 6d9a89b89f2..ec10d481e1d 100644 --- a/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml +++ b/src/tools/qml2puppet/mockfiles/qt6/EditView3D.qml @@ -1,9 +1,9 @@ // Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 -import QtQuick 6.0 -import QtQuick3D 6.0 -import MouseArea3D 1.0 +import QtQuick +import QtQuick3D +import MouseArea3D Item { id: viewRoot @@ -17,6 +17,8 @@ Item { property var overlayViews: [overlayView0, overlayView1, overlayView2, overlayView3] property var cameraControls: [cameraControl0, cameraControl1, cameraControl2, cameraControl3] property var viewRects: [viewRect0, viewRect1, viewRect2, viewRect3] + property var materialOverrides: [DebugSettings.None, DebugSettings.None, DebugSettings.None, DebugSettings.None] + property var showWireframes: [false, false, false, false] property var activeEditView: editViews[activeSplit] property var activeOverlayView: overlayViews[activeSplit] property string sceneId @@ -56,6 +58,7 @@ Item { signal commitObjectProperty(var objects, var propNames) signal changeObjectProperty(var objects, var propNames) signal notifyActiveSceneChange() + signal notifyActiveSplitChange(int index) onUsePerspectiveChanged: _generalHelper.storeToolState(sceneId, "usePerspective", usePerspective) onShowEditLightChanged: _generalHelper.storeToolState(sceneId, "showEditLight", showEditLight) @@ -68,6 +71,8 @@ Item { onShowParticleEmitterChanged: _generalHelper.storeToolState(sceneId, "showParticleEmitter", showParticleEmitter); onSelectionModeChanged: _generalHelper.storeToolState(sceneId, "selectionMode", selectionMode); onTransformModeChanged: _generalHelper.storeToolState(sceneId, "transformMode", transformMode); + onMaterialOverridesChanged: _generalHelper.storeToolState(sceneId, "matOverride", materialOverrides); + onShowWireframesChanged: _generalHelper.storeToolState(sceneId, "showWireframe", showWireframes); onSplitViewChanged: { _generalHelper.storeToolState(sceneId, "splitView", splitView); _generalHelper.requestOverlayUpdate(); @@ -75,6 +80,7 @@ Item { onActiveSplitChanged: { _generalHelper.storeToolState(sceneId, "activeSplit", activeSplit); cameraControls[activeSplit].forceActiveFocus(); + notifyActiveSplitChange(activeSplit); } onActiveSceneChanged: updateActiveScene() @@ -96,7 +102,9 @@ Item { "gridColor": gridColor, "importScene": activeScene, "cameraLookAt": cameraControls[i]._lookAtPoint, - "z": 1}); + "z": 1, + "sceneEnv.debugSettings.materialOverride": materialOverrides[i], + "sceneEnv.debugSettings.wireframeEnabled": showWireframes[i]}); editViews[i].usePerspective = Qt.binding(function() {return usePerspective;}); editViews[i].showSceneLight = Qt.binding(function() {return showEditLight;}); editViews[i].showGrid = Qt.binding(function() {return showGrid;}); @@ -107,6 +115,16 @@ Item { editViews[2].cameraLookAt = Qt.binding(function() {return cameraControl2._lookAtPoint;}); editViews[3].cameraLookAt = Qt.binding(function() {return cameraControl3._lookAtPoint;}); + editViews[0].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[0];}); + editViews[1].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[1];}); + editViews[2].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[2];}); + editViews[3].sceneEnv.debugSettings.materialOverride = Qt.binding(function() {return materialOverrides[3];}); + + editViews[0].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[0];}); + editViews[1].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[1];}); + editViews[2].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[2];}); + editViews[3].sceneEnv.debugSettings.wireframeEnabled = Qt.binding(function() {return showWireframes[3];}); + selectionBoxCount = 0; editViewsChanged(); return true; @@ -340,6 +358,16 @@ Item { activeSplit = toolStates.activeSplit; else if (resetToDefault) activeSplit = 0; + + if ("showWireframe" in toolStates) + showWireframes = toolStates.showWireframe; + else if (resetToDefault) + showWireframes = [false, false, false, false]; + + if ("matOverride" in toolStates) + materialOverrides = toolStates.matOverride; + else if (resetToDefault) + materialOverrides = [DebugSettings.None, DebugSettings.None, DebugSettings.None, DebugSettings.None]; } function storeCurrentToolStates() @@ -357,6 +385,8 @@ Item { _generalHelper.storeToolState(sceneId, "transformMode", transformMode); _generalHelper.storeToolState(sceneId, "splitView", splitView) _generalHelper.storeToolState(sceneId, "activeSplit", activeSplit) + _generalHelper.storeToolState(sceneId, "showWireframe", showWireframes) + _generalHelper.storeToolState(sceneId, "matOverride", materialOverrides) for (var i = 0; i < 4; ++i) cameraControls[i].storeCameraState(0); diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp index 63d4e79241b..180390def44 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.cpp @@ -881,6 +881,12 @@ void Qt5InformationNodeInstanceServer::handleActiveSceneChange() #endif } +void Qt5InformationNodeInstanceServer::handleActiveSplitChange(int index) +{ + nodeInstanceClient()->handlePuppetToCreatorCommand({PuppetToCreatorCommand::ActiveSplitChanged, + index}); +} + void Qt5InformationNodeInstanceServer::handleToolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState) @@ -1860,6 +1866,8 @@ void Qt5InformationNodeInstanceServer::setup3DEditView( this, SLOT(handleObjectPropertyChange(QVariant, QVariant))); QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSceneChange()), this, SLOT(handleActiveSceneChange())); + QObject::connect(m_editView3DData.rootItem, SIGNAL(notifyActiveSplitChange(int)), + this, SLOT(handleActiveSplitChange(int))); QObject::connect(&m_propertyChangeTimer, &QTimer::timeout, this, &Qt5InformationNodeInstanceServer::handleObjectPropertyChangeTimeout); QObject::connect(&m_selectionChangeTimer, &QTimer::timeout, @@ -2503,6 +2511,12 @@ void Qt5InformationNodeInstanceServer::view3DAction(const View3DActionCommand &c case View3DActionType::SplitViewToggle: updatedToolState.insert("splitView", command.isEnabled()); break; + case View3DActionType::ShowWireframe: + updatedToolState.insert("showWireframe", command.value().toList()); + break; + case View3DActionType::MaterialOverride: + updatedToolState.insert("matOverride", command.value().toList()); + break; default: break; diff --git a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h index 3ac044cbace..b603bb134eb 100644 --- a/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h +++ b/src/tools/qml2puppet/qml2puppet/instances/qt5informationnodeinstanceserver.h @@ -68,6 +68,7 @@ private slots: void handleObjectPropertyCommit(const QVariant &objects, const QVariant &propNames); void handleObjectPropertyChange(const QVariant &objects, const QVariant &propNames); void handleActiveSceneChange(); + void handleActiveSplitChange(int index); void handleToolStateChanged(const QString &sceneId, const QString &tool, const QVariant &toolState); void handleView3DSizeChange();