QmlDesigner: Refactor library icon generation for imported 3D assets

Previously, icon generation was done at import time, but that was
wasteful, as we now have image cache backed icon generation
available for component library icons. Added the few remaining missing
bits to support icon generation for image cache and disabled the old
icon generation implementation for Qt6.

A few issues in fit algorithm for preview image generation were also
uncovered and fixed to make icons render scene in comparable size to
the old version.

Qt5 imports still generate using old way since component library
3D previews generation doesn't work on Qt5.

Fixes: QDS-6205
Change-Id: I5418fa19d86e81adcd184be023f1dfbc813d0bf5
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Samuel Ghinet <samuel.ghinet@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Miikka Heikkinen
2022-05-27 14:14:22 +03:00
parent 46049bac32
commit 80ea026fd8
19 changed files with 132 additions and 127 deletions

View File

@@ -32,7 +32,6 @@
<file>mockfiles/qt6/FadeHandle.qml</file>
<file>mockfiles/qt6/HelperGrid.qml</file>
<file>mockfiles/qt6/IconGizmo.qml</file>
<file>mockfiles/qt6/IconRenderer3D.qml</file>
<file>mockfiles/qt6/LightGizmo.qml</file>
<file>mockfiles/qt6/LightIconGizmo.qml</file>
<file>mockfiles/qt6/LightModel.qml</file>

View File

@@ -32,7 +32,7 @@ View3D {
property Material previewMaterial
function fitToViewPort()
function fitToViewPort(closeUp)
{
// No need to zoom this view, this is here just to avoid runtime warnings
}

View File

@@ -41,6 +41,8 @@ Item {
property var modelViewComponent
property var nodeViewComponent
property bool closeUp: false
function destroyView()
{
previewObject = null;
@@ -96,7 +98,15 @@ Item {
function fitToViewPort()
{
view.fitToViewPort();
view.fitToViewPort(closeUp);
}
// Enables/disables icon mode. When in icon mode, camera is zoomed bit closer to reduce margins
// and the background is removed, in order to generate a preview suitable for library icons.
function setIconMode(enable)
{
closeUp = enable;
backgroundRect.visible = !enable;
}
View3D {
@@ -108,10 +118,15 @@ Item {
id: contentItem
anchors.fill: parent
Rectangle {
Item {
id: viewRect
anchors.fill: parent
}
Rectangle {
id: backgroundRect
anchors.fill: parent
z: -1
gradient: Gradient {
GradientStop { position: 1.0; color: "#222222" }
GradientStop { position: 0.0; color: "#999999" }

View File

@@ -34,11 +34,11 @@ View3D {
property Model sourceModel
function fitToViewPort()
function fitToViewPort(closeUp)
{
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, sourceModel, root,
1040, closeUp);
}
SceneEnvironment {

View File

@@ -32,11 +32,11 @@ View3D {
environment: sceneEnv
camera: theCamera
function fitToViewPort()
function fitToViewPort(closeUp)
{
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
1040, closeUp);
}
SceneEnvironment {

View File

@@ -1,88 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
****************************************************************************/
import QtQuick 6.0
import QtQuick3D 6.0
Item {
id: viewRoot
width: 1024
height: 1024
visible: true
property alias view3D: view3D
property alias camPos: viewCamera.position
function setSceneToBox()
{
selectionBox.targetNode = view3D.importScene;
}
function fitAndHideBox()
{
cameraControl.focusObject(selectionBox.model, viewCamera.eulerRotation, true, true);
if (cameraControl._zoomFactor < 0.1)
view3D.importScene.scale = view3D.importScene.scale.times(10);
if (cameraControl._zoomFactor > 10)
view3D.importScene.scale = view3D.importScene.scale.times(0.1);
selectionBox.visible = false;
}
View3D {
id: view3D
camera: viewCamera
environment: sceneEnv
SceneEnvironment {
id: sceneEnv
antialiasingMode: SceneEnvironment.MSAA
antialiasingQuality: SceneEnvironment.VeryHigh
}
PerspectiveCamera {
id: viewCamera
position: Qt.vector3d(-200, 200, 200)
eulerRotation: Qt.vector3d(-45, -45, 0)
}
DirectionalLight {
rotation: viewCamera.rotation
}
SelectionBox {
id: selectionBox
view3D: view3D
geometryName: "SB"
}
EditCameraController {
id: cameraControl
camera: view3D.camera
view3d: view3D
ignoreToolState: true
}
}
}

View File

@@ -32,7 +32,7 @@ View3D {
property Material previewMaterial
function fitToViewPort()
function fitToViewPort(closeUp)
{
// No need to zoom this view, this is here just to avoid runtime warnings
}

View File

@@ -41,6 +41,8 @@ Item {
property var modelViewComponent
property var nodeViewComponent
property bool closeUp: false
function destroyView()
{
previewObject = null;
@@ -96,17 +98,30 @@ Item {
function fitToViewPort()
{
view.fitToViewPort();
view.fitToViewPort(closeUp);
}
// Enables/disables icon mode. When in icon mode, camera is zoomed bit closer to reduce margins
// and the background is removed, in order to generate a preview suitable for library icons.
function setIconMode(enable)
{
closeUp = enable;
backgroundRect.visible = !enable;
}
Item {
id: contentItem
anchors.fill: parent
Rectangle {
Item {
id: viewRect
anchors.fill: parent
}
Rectangle {
id: backgroundRect
anchors.fill: parent
z: -1
gradient: Gradient {
GradientStop { position: 1.0; color: "#222222" }
GradientStop { position: 0.0; color: "#999999" }

View File

@@ -34,11 +34,11 @@ View3D {
property Model sourceModel
function fitToViewPort()
function fitToViewPort(closeUp)
{
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, sourceModel, root,
1040);
1040, closeUp);
}
SceneEnvironment {

View File

@@ -32,11 +32,11 @@ View3D {
environment: sceneEnv
camera: theCamera
function fitToViewPort()
function fitToViewPort(closeUp)
{
// The magic number is the distance from camera default pos to origin
_generalHelper.calculateNodeBoundsAndFocusCamera(theCamera, importScene, root,
1040);
1040, closeUp);
}
SceneEnvironment {

View File

@@ -308,7 +308,7 @@ QVector4D GeneralHelper::focusNodesToCamera(QQuick3DCamera *camera, float defaul
// and recalculating bounds for every frame is not a problem.
void GeneralHelper::calculateNodeBoundsAndFocusCamera(
QQuick3DCamera *camera, QQuick3DNode *node, QQuick3DViewport *viewPort,
float defaultLookAtDistance)
float defaultLookAtDistance, bool closeUp)
{
QVector3D minBounds;
QVector3D maxBounds;
@@ -317,7 +317,9 @@ void GeneralHelper::calculateNodeBoundsAndFocusCamera(
QVector3D extents = maxBounds - minBounds;
QVector3D lookAt = minBounds + (extents / 2.f);
float maxExtent = qMax(extents.x(), qMax(extents.y(), extents.z()));
float maxExtent = qSqrt(qreal(extents.x()) * qreal(extents.x())
+ qreal(extents.y()) * qreal(extents.y())
+ qreal(extents.z()) * qreal(extents.z()));
// Reset camera position to default zoom
QMatrix4x4 m = camera->sceneTransform();
@@ -328,9 +330,27 @@ void GeneralHelper::calculateNodeBoundsAndFocusCamera(
camera->setPosition(lookAt + newLookVector);
float newZoomFactor = maxExtent / 725.f; // Divisor taken from focusNodesToCamera function
// CloseUp divisor is used for icon generation, where we can allow some extreme models to go
// slightly out of bounds for better results generally. The other divisor is used for other
// previews, where the image is larger to begin with and we would also like some margin
// between preview edge and the rendered model, so we can be more conservative with the zoom.
// The divisor values are empirically selected to provide nice result.
float divisor = closeUp ? 1250.f : 1050.f;
float newZoomFactor = maxExtent / divisor;
zoomCamera(viewPort, camera, 0, defaultLookAtDistance, lookAt, newZoomFactor, false);
if (auto perspectiveCamera = qobject_cast<QQuick3DPerspectiveCamera *>(camera)) {
// Fix camera near/far clips in case we are dealing with extreme zooms
const float cameraDist = qAbs((camera->position() - lookAt).length());
const float minDist = cameraDist - (maxExtent / 2.f);
const float maxDist = cameraDist + (maxExtent / 2.f);
if (minDist < perspectiveCamera->clipNear() || maxDist > perspectiveCamera->clipFar()) {
perspectiveCamera->setClipNear(minDist * 0.99);
perspectiveCamera->setClipFar(maxDist * 1.01);
}
}
}
// Aligns any cameras found in nodes list to a camera.
@@ -823,12 +843,13 @@ bool GeneralHelper::getBounds(QQuick3DViewport *view3D, QQuick3DNode *node, QVec
if (auto childNode = qobject_cast<QQuick3DNode *>(child)) {
QVector3D newMinBounds = minBounds;
QVector3D newMaxBounds = maxBounds;
hasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds, true);
bool childHasModel = getBounds(view3D, childNode, newMinBounds, newMaxBounds, true);
// Ignore any subtrees that do not have Model in them as we don't need those
// for visual bounds calculations
if (hasModel) {
if (childHasModel) {
minBoundsVec << newMinBounds;
maxBoundsVec << newMaxBounds;
hasModel = true;
}
}
}

View File

@@ -77,7 +77,7 @@ public:
bool closeUp = false);
Q_INVOKABLE void calculateNodeBoundsAndFocusCamera(QQuick3DCamera *camera, QQuick3DNode *node,
QQuick3DViewport *viewPort,
float defaultLookAtDistance);
float defaultLookAtDistance, bool closeUp);
Q_INVOKABLE void alignCameras(QQuick3DCamera *camera, const QVariant &nodes);
Q_INVOKABLE QVector3D alignView(QQuick3DCamera *camera, const QVariant &nodes,
const QVector3D &lookAtPoint);

View File

@@ -57,6 +57,9 @@ void Quick3DRenderableNodeInstance::initialize(const ObjectNodeInstance::Pointer
// In case this is the scene root, we need to create a dummy View3D for the scene
// in preview puppets
if (instanceId() == 0 && (!nodeInstanceServer()->isInformationServer())) {
nodeInstanceServer()->quickWindow()->setDefaultAlphaBuffer(true);
nodeInstanceServer()->quickWindow()->setColor(Qt::transparent);
auto helper = new QmlDesigner::Internal::GeneralHelper();
engine()->rootContext()->setContextProperty("_generalHelper", helper);
@@ -199,6 +202,14 @@ QQuickItem *Quick3DRenderableNodeInstance::contentItem() const
return m_dummyRootView;
}
void Quick3DRenderableNodeInstance::setPropertyVariant(const PropertyName &name,
const QVariant &value)
{
if (m_dummyRootView && name == "isLibraryIcon")
QMetaObject::invokeMethod(m_dummyRootView, "setIconMode", Q_ARG(QVariant, value));
ObjectNodeInstance::setPropertyVariant(name, value);
}
Qt5NodeInstanceServer *Quick3DRenderableNodeInstance::qt5NodeInstanceServer() const
{
return qobject_cast<Qt5NodeInstanceServer *>(nodeInstanceServer());

View File

@@ -52,6 +52,7 @@ public:
QList<ServerNodeInstance> stateInstances() const override;
QQuickItem *contentItem() const override;
void setPropertyVariant(const PropertyName &name, const QVariant &value) override;
protected:
explicit Quick3DRenderableNodeInstance(QObject *node);

View File

@@ -401,7 +401,6 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
qmlInfo.append(outDir.relativeFilePath(qmlIt.filePath()));
qmlInfo.append('\n');
// Generate item library icon for qml file based on root component
QFile qmlFile(qmlIt.filePath());
if (qmlFile.open(QIODevice::ReadOnly)) {
QString iconFileName = outDir.path() + '/'
@@ -410,6 +409,8 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
QString iconFileName2x = iconFileName + "@2x";
QByteArray content = qmlFile.readAll();
int braceIdx = content.indexOf('{');
QString impVersionStr;
int impVersionMajor = -1;
if (braceIdx != -1) {
int nlIdx = content.lastIndexOf('\n', braceIdx);
QByteArray rootItem = content.mid(nlIdx, braceIdx - nlIdx).trimmed();
@@ -423,8 +424,9 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
out << "canBeDroppedInView3D: true" << Qt::endl;
file.close();
// Add quick3D import unless it is already added
if (m_requiredImports.first().url() != "QtQuick3D") {
// Assume that all assets import the same QtQuick3D version,
// since they are being imported with same kit
if (impVersionMajor == -1) {
QByteArray import3dStr{"import QtQuick3D"};
int importIdx = content.indexOf(import3dStr);
if (importIdx != -1 && importIdx < braceIdx) {
@@ -433,15 +435,25 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd)
QByteArray versionStr = content.mid(importIdx, nlIdx - importIdx).trimmed();
// There could be 'as abc' after version, so just take first part
QList<QByteArray> parts = versionStr.split(' ');
QString impVersion;
if (parts.size() >= 1)
impVersion = QString::fromUtf8(parts[0]);
if (parts.size() >= 1) {
impVersionStr = QString::fromUtf8(parts[0]);
if (impVersionStr.isEmpty())
impVersionMajor = 6;
else
impVersionMajor = impVersionStr.left(1).toInt();
}
}
}
// Add quick3D import unless it is already added
if (impVersionMajor > 0
&& m_requiredImports.first().url() != "QtQuick3D") {
m_requiredImports.prepend(Import::createLibraryImport(
"QtQuick3D", impVersion));
"QtQuick3D", impVersionStr));
}
}
}
if (startIconProcess(24, iconFileName, qmlIt.filePath())) {
if (impVersionMajor > 0 && impVersionMajor < 6
&& startIconProcess(24, iconFileName, qmlIt.filePath())) {
// Since icon is generated by external process, the file won't be
// ready for asset gathering below, so assume its generation succeeds
// and add it now.

View File

@@ -92,7 +92,9 @@ QQuickImageResponse *ItemLibraryIconImageProvider::requestImageResponse(const QS
}
},
Qt::QueuedConnection);
});
},
"libIcon",
ImageCache::LibraryIconAuxiliaryData{true});
return response.release();
}

View File

@@ -74,7 +74,7 @@ ImageCacheCollector::~ImageCacheCollector() = default;
void ImageCacheCollector::start(Utils::SmallStringView name,
Utils::SmallStringView state,
const ImageCache::AuxiliaryData &,
const ImageCache::AuxiliaryData &auxiliaryData,
CaptureCallback captureCallback,
AbortCallback abortCallback)
{
@@ -96,13 +96,22 @@ void ImageCacheCollector::start(Utils::SmallStringView name,
model->setRewriterView(&rewriterView);
bool is3DRoot = !rewriterView.inErrorState()
&& (rewriterView.rootModelNode().isSubclassOf("Quick3D.Node")
|| rewriterView.rootModelNode().isSubclassOf("Quick3D.Material"));
if (rewriterView.inErrorState() || (!rewriterView.rootModelNode().metaInfo().isGraphicalItem()
&& !rewriterView.rootModelNode().isSubclassOf("Quick3D.Node") )) {
&& !is3DRoot)) {
if (abortCallback)
abortCallback(ImageCache::AbortReason::Failed);
return;
}
if (is3DRoot) {
if (auto libIcon = Utils::get_if<ImageCache::LibraryIconAuxiliaryData>(&auxiliaryData))
rewriterView.rootModelNode().setAuxiliaryData("isLibraryIcon@NodeInstance", libIcon->enable);
}
ModelNode stateNode = rewriterView.modelNodeForId(QString{state});
if (stateNode.isValid())

View File

@@ -54,7 +54,16 @@ public:
QString text;
};
using AuxiliaryData = Utils::variant<Utils::monostate, FontCollectorSizeAuxiliaryData, FontCollectorSizesAuxiliaryData>;
class LibraryIconAuxiliaryData
{
public:
bool enable;
};
using AuxiliaryData = Utils::variant<Utils::monostate,
LibraryIconAuxiliaryData,
FontCollectorSizeAuxiliaryData,
FontCollectorSizesAuxiliaryData>;
enum class AbortReason : char { Abort, Failed };

View File

@@ -445,10 +445,9 @@ void SubComponentManager::parseQuick3DAssetsItem(const QString &importUrl, const
QString iconPath = qmlIt.fileInfo().absolutePath() + '/'
+ Constants::QUICK_3D_ASSET_ICON_DIR + '/' + name
+ Constants::QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX;
if (!QFileInfo::exists(iconPath))
iconPath = defaultIconPath;
if (QFileInfo::exists(iconPath))
itemLibraryEntry.setLibraryEntryIconPath(iconPath);
itemLibraryEntry.setTypeIcon(QIcon(iconPath));
itemLibraryEntry.setTypeIcon(QIcon(defaultIconPath));
// load hints file if exists
QFile hintsFile(qmlIt.fileInfo().absolutePath() + '/' + name + ".hints");