forked from qt-creator/qt-creator
QmlDesigner: Add light meshes to edit 3d view
Change-Id: I996498fde14510fc78c729f56cb8d46b28b233bb Fixes: QDS-1634 Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io> Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
This commit is contained in:
@@ -24,11 +24,12 @@
|
||||
****************************************************************************/
|
||||
|
||||
import QtQuick 2.0
|
||||
import QtQuick3D 1.15
|
||||
|
||||
IconGizmo {
|
||||
id: cameraGizmo
|
||||
|
||||
property var frustumModel: null
|
||||
property Model frustumModel: null
|
||||
|
||||
iconSource: "qrc:///qtquickplugin/mockfiles/images/editor_camera.png"
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ Item {
|
||||
function fitToView()
|
||||
{
|
||||
if (editView) {
|
||||
var targetNode = selectedNodes.length > 0
|
||||
var targetNode = selectionBoxes.length > 0
|
||||
? selectionBoxes[0].model : null;
|
||||
cameraControl.focusObject(targetNode, editView.camera.eulerRotation, true);
|
||||
}
|
||||
@@ -301,16 +301,20 @@ Item {
|
||||
}
|
||||
|
||||
// No free gizmos available, create a new one
|
||||
var component = Qt.createComponent("LightGizmo.qml");
|
||||
if (component.status === Component.Ready) {
|
||||
var gizmo = component.createObject(overlayView,
|
||||
{"view3D": overlayView, "targetNode": obj,
|
||||
"selectedNodes": selectedNodes, "scene": scene,
|
||||
"activeScene": activeScene});
|
||||
var gizmoComponent = Qt.createComponent("LightGizmo.qml");
|
||||
var modelComponent = Qt.createComponent("LightModel.qml");
|
||||
if (gizmoComponent.status === Component.Ready && modelComponent.status === Component.Ready) {
|
||||
var geometryName = _generalHelper.generateUniqueName("LightGeometry");
|
||||
var model = modelComponent.createObject(overlayScene, {"geometryName": geometryName});
|
||||
var gizmo = gizmoComponent.createObject(overlayView,
|
||||
{"view3D": overlayView, "targetNode": obj,
|
||||
"selectedNodes": selectedNodes, "scene": scene,
|
||||
"activeScene": activeScene});
|
||||
lightGizmos[lightGizmos.length] = gizmo;
|
||||
gizmo.clicked.connect(handleObjectClicked);
|
||||
gizmo.selectedNodes = Qt.binding(function() {return selectedNodes;});
|
||||
gizmo.activeScene = Qt.binding(function() {return activeScene;});
|
||||
gizmo.connectModel(model);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ import QtQuick3D 1.15
|
||||
IconGizmo {
|
||||
id: lightGizmo
|
||||
|
||||
property Model lightModel: null
|
||||
|
||||
iconSource: targetNode
|
||||
? targetNode instanceof DirectionalLight
|
||||
? "qrc:///qtquickplugin/mockfiles/images/directional_light_gradient.png"
|
||||
@@ -39,4 +41,26 @@ IconGizmo {
|
||||
|
||||
// ColorOverlay doesn't work correctly with hidden windows so commenting it out for now
|
||||
//overlayColor: targetNode ? targetNode.color : "transparent"
|
||||
|
||||
function connectModel(model)
|
||||
{
|
||||
lightModel = model;
|
||||
|
||||
model.selected = selected;
|
||||
model.selected = Qt.binding(function() {return selected;});
|
||||
|
||||
model.scene = scene;
|
||||
model.scene = Qt.binding(function() {return scene;});
|
||||
|
||||
model.targetNode = targetNode;
|
||||
model.targetNode = Qt.binding(function() {return targetNode;});
|
||||
|
||||
model.visible = visible;
|
||||
model.visible = Qt.binding(function() {return visible;});
|
||||
}
|
||||
|
||||
onActiveSceneChanged: {
|
||||
if (lightModel && activeScene == scene)
|
||||
lightModel.updateGeometry();
|
||||
}
|
||||
}
|
||||
|
||||
62
share/qtcreator/qml/qmlpuppet/mockfiles/LightModel.qml
Normal file
62
share/qtcreator/qml/qmlpuppet/mockfiles/LightModel.qml
Normal file
@@ -0,0 +1,62 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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 2.0
|
||||
import QtQuick3D 1.15
|
||||
import LightGeometry 1.0
|
||||
|
||||
Model {
|
||||
id: lightModel
|
||||
|
||||
property string geometryName
|
||||
property alias geometryName: lightGeometry.name // Name must be unique for each geometry
|
||||
property Node targetNode: null
|
||||
property Node scene: null
|
||||
property bool selected: false
|
||||
|
||||
function updateGeometry()
|
||||
{
|
||||
lightGeometry.update();
|
||||
}
|
||||
|
||||
position: targetNode ? targetNode.scenePosition : Qt.vector3d(0, 0, 0)
|
||||
rotation: targetNode ? targetNode.sceneRotation : Qt.quaternion(1, 0, 0, 0)
|
||||
scale: Qt.vector3d(50, 50, 50)
|
||||
|
||||
geometry: lightGeometry
|
||||
materials: [
|
||||
DefaultMaterial {
|
||||
id: defaultMaterial
|
||||
emissiveColor: lightModel.selected ? "#FF0000" : "#555555"
|
||||
lighting: DefaultMaterial.NoLighting
|
||||
cullMode: Material.NoCulling
|
||||
}
|
||||
]
|
||||
|
||||
LightGeometry {
|
||||
id: lightGeometry
|
||||
light: lightModel.scene && lightModel.targetNode ? lightModel.targetNode : null
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ void CameraGeometry::fillVertexData(QByteArray &vertexData, QByteArray &indexDat
|
||||
{
|
||||
const int vertexSize = int(sizeof(float)) * 8 * 3; // 8 vertices, 3 floats/vert
|
||||
vertexData.resize(vertexSize);
|
||||
const int indexSize = int(sizeof(quint16)) * 12 * 2; // 16 lines, 2 vert/line
|
||||
const int indexSize = int(sizeof(quint16)) * 12 * 2; // 12 lines, 2 vert/line
|
||||
indexData.resize(indexSize);
|
||||
|
||||
auto dataPtr = reinterpret_cast<float *>(vertexData.data());
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
HEADERS += $$PWD/generalhelper.h \
|
||||
$$PWD/mousearea3d.h \
|
||||
$$PWD/camerageometry.h \
|
||||
$$PWD/lightgeometry.h \
|
||||
$$PWD/gridgeometry.h \
|
||||
$$PWD/selectionboxgeometry.h \
|
||||
$$PWD/linegeometry.h
|
||||
@@ -8,6 +9,7 @@ HEADERS += $$PWD/generalhelper.h \
|
||||
SOURCES += $$PWD/generalhelper.cpp \
|
||||
$$PWD/mousearea3d.cpp \
|
||||
$$PWD/camerageometry.cpp \
|
||||
$$PWD/lightgeometry.cpp \
|
||||
$$PWD/gridgeometry.cpp \
|
||||
$$PWD/selectionboxgeometry.cpp \
|
||||
$$PWD/linegeometry.cpp
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef QUICK3D_MODULE
|
||||
|
||||
#include "lightgeometry.h"
|
||||
|
||||
#include <QtQuick3DRuntimeRender/private/qssgrendergeometry_p.h>
|
||||
#include <QtQuick3DRuntimeRender/private/qssgrenderlight_p.h>
|
||||
#include <QtQuick3D/private/qquick3darealight_p.h>
|
||||
#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
|
||||
#include <QtQuick3D/private/qquick3dpointlight_p.h>
|
||||
#include <QtCore/qmath.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace QmlDesigner {
|
||||
namespace Internal {
|
||||
|
||||
LightGeometry::LightGeometry()
|
||||
: QQuick3DGeometry()
|
||||
{
|
||||
}
|
||||
|
||||
LightGeometry::~LightGeometry()
|
||||
{
|
||||
}
|
||||
|
||||
QQuick3DAbstractLight *QmlDesigner::Internal::LightGeometry::light() const
|
||||
{
|
||||
return m_light;
|
||||
}
|
||||
|
||||
void QmlDesigner::Internal::LightGeometry::setLight(QQuick3DAbstractLight *light)
|
||||
{
|
||||
if (m_light == light)
|
||||
return;
|
||||
|
||||
m_light = light;
|
||||
|
||||
emit lightChanged();
|
||||
update();
|
||||
}
|
||||
|
||||
QSSGRenderGraphObject *LightGeometry::updateSpatialNode(QSSGRenderGraphObject *node)
|
||||
{
|
||||
if (!m_light)
|
||||
return node;
|
||||
|
||||
node = QQuick3DGeometry::updateSpatialNode(node);
|
||||
QSSGRenderGeometry *geometry = static_cast<QSSGRenderGeometry *>(node);
|
||||
|
||||
geometry->clear();
|
||||
|
||||
QByteArray vertexData;
|
||||
QByteArray indexData;
|
||||
QVector3D minBounds;
|
||||
QVector3D maxBounds;
|
||||
fillVertexData(vertexData, indexData, minBounds, maxBounds);
|
||||
|
||||
geometry->addAttribute(QSSGRenderGeometry::Attribute::PositionSemantic, 0,
|
||||
QSSGRenderGeometry::Attribute::ComponentType::F32Type);
|
||||
geometry->addAttribute(QSSGRenderGeometry::Attribute::IndexSemantic, 0,
|
||||
QSSGRenderGeometry::Attribute::ComponentType::U16Type);
|
||||
geometry->setStride(12);
|
||||
geometry->setVertexData(vertexData);
|
||||
geometry->setIndexData(indexData);
|
||||
geometry->setPrimitiveType(QSSGRenderGeometry::Lines);
|
||||
geometry->setBounds(minBounds, maxBounds);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void LightGeometry::fillVertexData(QByteArray &vertexData, QByteArray &indexData,
|
||||
QVector3D &minBounds, QVector3D &maxBounds)
|
||||
{
|
||||
int vertexSize = 0;
|
||||
int indexSize = 0;
|
||||
const int dirSegments = 12;
|
||||
const int pointLightDensity = 5;
|
||||
|
||||
if (qobject_cast<QQuick3DAreaLight *>(m_light)) {
|
||||
// Area light model is a rectangle with perpendicular lines on corners
|
||||
vertexSize = int(sizeof(float)) * 3 * 8;
|
||||
indexSize = int(sizeof(quint16)) * 8 * 2;
|
||||
} else if (qobject_cast<QQuick3DDirectionalLight *>(m_light)) {
|
||||
// Directional light model is a circle with perpendicular lines on circumference vertices
|
||||
vertexSize = int(sizeof(float)) * 3 * dirSegments * 2;
|
||||
indexSize = int(sizeof(quint16)) * dirSegments * 2 * 2;
|
||||
} else if (qobject_cast<QQuick3DPointLight *>(m_light)) {
|
||||
// Point light model is a set of lines radiating from central point.
|
||||
// We reserve more than we need so we don't have to calculate the actual need here,
|
||||
// and resize later when we know the exact count.
|
||||
vertexSize = int(sizeof(float)) * 3 * pointLightDensity * pointLightDensity * 4;
|
||||
indexSize = int(sizeof(quint16)) * pointLightDensity * pointLightDensity * 4;
|
||||
}
|
||||
vertexData.resize(vertexSize);
|
||||
indexData.resize(indexSize);
|
||||
|
||||
auto dataPtr = reinterpret_cast<float *>(vertexData.data());
|
||||
auto indexPtr = reinterpret_cast<quint16 *>(indexData.data());
|
||||
|
||||
if (qobject_cast<QQuick3DAreaLight *>(m_light)) {
|
||||
*dataPtr++ = -1.f; *dataPtr++ = 1.f; *dataPtr++ = 0.f;
|
||||
*dataPtr++ = -1.f; *dataPtr++ = -1.f; *dataPtr++ = 0.f;
|
||||
*dataPtr++ = 1.f; *dataPtr++ = -1.f; *dataPtr++ = 0.f;
|
||||
*dataPtr++ = 1.f; *dataPtr++ = 1.f; *dataPtr++ = 0.f;
|
||||
|
||||
*dataPtr++ = -1.f; *dataPtr++ = 1.f; *dataPtr++ = -1.f;
|
||||
*dataPtr++ = -1.f; *dataPtr++ = -1.f; *dataPtr++ = -1.f;
|
||||
*dataPtr++ = 1.f; *dataPtr++ = -1.f; *dataPtr++ = -1.f;
|
||||
*dataPtr++ = 1.f; *dataPtr++ = 1.f; *dataPtr++ = -1.f;
|
||||
|
||||
*indexPtr++ = 0; *indexPtr++ = 1;
|
||||
*indexPtr++ = 1; *indexPtr++ = 2;
|
||||
*indexPtr++ = 2; *indexPtr++ = 3;
|
||||
*indexPtr++ = 3; *indexPtr++ = 0;
|
||||
|
||||
*indexPtr++ = 0; *indexPtr++ = 4;
|
||||
*indexPtr++ = 1; *indexPtr++ = 5;
|
||||
*indexPtr++ = 2; *indexPtr++ = 6;
|
||||
*indexPtr++ = 3; *indexPtr++ = 7;
|
||||
} else if (qobject_cast<QQuick3DDirectionalLight *>(m_light)) {
|
||||
const double segment = M_PI * 2. / double(dirSegments);
|
||||
for (quint16 i = 0; i < dirSegments; ++i) {
|
||||
float x = float(qCos(i * segment));
|
||||
float y = float(qSin(i * segment));
|
||||
*dataPtr++ = x; *dataPtr++ = y; *dataPtr++ = 0.f;
|
||||
*dataPtr++ = x; *dataPtr++ = y; *dataPtr++ = -1.f;
|
||||
const quint16 base = i * 2;
|
||||
*indexPtr++ = base; *indexPtr++ = base + 1;
|
||||
*indexPtr++ = base; *indexPtr++ = base + 2;
|
||||
}
|
||||
// Adjust the final index to complete the circle
|
||||
*(--indexPtr) = 0;
|
||||
} else if (qobject_cast<QQuick3DPointLight *>(m_light)) {
|
||||
const double innerRad = .3;
|
||||
vertexSize = 0;
|
||||
indexSize = 0;
|
||||
int vertexIndex = 0;
|
||||
for (quint16 i = 0; i < pointLightDensity; ++i) {
|
||||
double latAngle = (((.9 / (pointLightDensity - 1)) * i) + .05) * M_PI;
|
||||
quint16 longPoints = pointLightDensity * 2 * qSin(latAngle);
|
||||
latAngle -= M_PI_2;
|
||||
const double longSegment = M_PI * 2. / double(longPoints);
|
||||
for (quint16 j = 0; j < longPoints; ++j) {
|
||||
double longAngle = longSegment * j;
|
||||
float q = float(qCos(latAngle));
|
||||
float x = float(qCos(longAngle) * q);
|
||||
float y = float(qSin(latAngle));
|
||||
float z = float(qSin(longAngle) * q);
|
||||
|
||||
*dataPtr++ = x * innerRad; *dataPtr++ = y * innerRad; *dataPtr++ = z * innerRad;
|
||||
*dataPtr++ = x; *dataPtr++ = y; *dataPtr++ = z;
|
||||
*indexPtr++ = vertexIndex; *indexPtr++ = vertexIndex + 1;
|
||||
|
||||
vertexIndex += 2;
|
||||
vertexSize += 6 * sizeof(float);
|
||||
indexSize += 2 * sizeof(quint16);
|
||||
}
|
||||
}
|
||||
|
||||
vertexData.resize(vertexSize);
|
||||
indexData.resize(indexSize);
|
||||
}
|
||||
|
||||
static const float floatMin = std::numeric_limits<float>::lowest();
|
||||
static const float floatMax = std::numeric_limits<float>::max();
|
||||
auto vertexPtr = reinterpret_cast<QVector3D *>(vertexData.data());
|
||||
minBounds = QVector3D(floatMax, floatMax, floatMax);
|
||||
maxBounds = QVector3D(floatMin, floatMin, floatMin);
|
||||
for (int i = 0; i < vertexSize / 12; ++i) {
|
||||
minBounds[0] = qMin((*vertexPtr)[0], minBounds[0]);
|
||||
minBounds[1] = qMin((*vertexPtr)[1], minBounds[1]);
|
||||
minBounds[2] = qMin((*vertexPtr)[2], minBounds[2]);
|
||||
maxBounds[0] = qMax((*vertexPtr)[0], maxBounds[0]);
|
||||
maxBounds[1] = qMax((*vertexPtr)[1], maxBounds[1]);
|
||||
maxBounds[2] = qMax((*vertexPtr)[2], maxBounds[2]);
|
||||
++vertexPtr;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // QUICK3D_MODULE
|
||||
@@ -0,0 +1,67 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef QUICK3D_MODULE
|
||||
|
||||
#include <QtQuick3D/private/qquick3dgeometry_p.h>
|
||||
#include <QtQuick3D/private/qquick3dabstractlight_p.h>
|
||||
|
||||
namespace QmlDesigner {
|
||||
namespace Internal {
|
||||
|
||||
class LightGeometry : public QQuick3DGeometry
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QQuick3DAbstractLight *light READ light WRITE setLight NOTIFY lightChanged)
|
||||
|
||||
public:
|
||||
LightGeometry();
|
||||
~LightGeometry() override;
|
||||
|
||||
QQuick3DAbstractLight *light() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void setLight(QQuick3DAbstractLight *light);
|
||||
|
||||
Q_SIGNALS:
|
||||
void lightChanged();
|
||||
|
||||
protected:
|
||||
QSSGRenderGraphObject *updateSpatialNode(QSSGRenderGraphObject *node) override;
|
||||
|
||||
private:
|
||||
void fillVertexData(QByteArray &vertexData, QByteArray &indexData,
|
||||
QVector3D &minBounds, QVector3D &maxBounds);
|
||||
QQuick3DAbstractLight *m_light = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
QML_DECLARE_TYPE(QmlDesigner::Internal::LightGeometry)
|
||||
|
||||
#endif // QUICK3D_MODULE
|
||||
@@ -67,6 +67,7 @@
|
||||
#include "../editor3d/generalhelper.h"
|
||||
#include "../editor3d/mousearea3d.h"
|
||||
#include "../editor3d/camerageometry.h"
|
||||
#include "../editor3d/lightgeometry.h"
|
||||
#include "../editor3d/gridgeometry.h"
|
||||
#include "../editor3d/selectionboxgeometry.h"
|
||||
#include "../editor3d/linegeometry.h"
|
||||
@@ -104,6 +105,7 @@ void Qt5InformationNodeInstanceServer::createEditView3D()
|
||||
qmlRegisterRevision<QQuick3DNode, 1>("MouseArea3D", 1, 0);
|
||||
qmlRegisterType<QmlDesigner::Internal::MouseArea3D>("MouseArea3D", 1, 0, "MouseArea3D");
|
||||
qmlRegisterType<QmlDesigner::Internal::CameraGeometry>("CameraGeometry", 1, 0, "CameraGeometry");
|
||||
qmlRegisterType<QmlDesigner::Internal::LightGeometry>("LightGeometry", 1, 0, "LightGeometry");
|
||||
qmlRegisterType<QmlDesigner::Internal::GridGeometry>("GridGeometry", 1, 0, "GridGeometry");
|
||||
qmlRegisterType<QmlDesigner::Internal::SelectionBoxGeometry>("SelectionBoxGeometry", 1, 0, "SelectionBoxGeometry");
|
||||
qmlRegisterType<QmlDesigner::Internal::LineGeometry>("LineGeometry", 1, 0, "LineGeometry");
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<file>mockfiles/MoveGizmo.qml</file>
|
||||
<file>mockfiles/CameraFrustum.qml</file>
|
||||
<file>mockfiles/CameraGizmo.qml</file>
|
||||
<file>mockfiles/LightModel.qml</file>
|
||||
<file>mockfiles/LightGizmo.qml</file>
|
||||
<file>mockfiles/IconGizmo.qml</file>
|
||||
<file>mockfiles/Overlay2D.qml</file>
|
||||
|
||||
Reference in New Issue
Block a user