mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Read/write 3D renderers of map layers to project files
This commit is contained in:
parent
04b00aad6e
commit
6df6681326
@ -453,6 +453,12 @@ Invoked by QgsProject.read().
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
virtual void resolveReferences( QgsProject *project );
|
||||
%Docstring
|
||||
Resolve references to other layers (kept as layer IDs after reading XML) into layer objects.
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
QStringList customPropertyKeys() const;
|
||||
%Docstring
|
||||
Returns list of all keys within custom properties. Properties are stored in a map and saved in project file.
|
||||
@ -1091,7 +1097,6 @@ Set whether layer is valid or not - should be used in constructor.
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
|
||||
void readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith = QString() );
|
||||
%Docstring
|
||||
Read custom properties from project file.
|
||||
|
@ -711,7 +711,7 @@ Return the provider type for this layer
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
void resolveReferences( QgsProject *project );
|
||||
virtual void resolveReferences( QgsProject *project );
|
||||
%Docstring
|
||||
Resolve references to other layers (kept as layer IDs after reading XML) into layer objects.
|
||||
.. versionadded:: 3.0
|
||||
|
@ -16,8 +16,10 @@ Abstract3DSymbol *Polygon3DSymbol::clone() const
|
||||
return new Polygon3DSymbol( *this );
|
||||
}
|
||||
|
||||
void Polygon3DSymbol::writeXml( QDomElement &elem ) const
|
||||
void Polygon3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomDocument doc = elem.ownerDocument();
|
||||
|
||||
QDomElement elemDataProperties = doc.createElement( "data" );
|
||||
@ -32,8 +34,10 @@ void Polygon3DSymbol::writeXml( QDomElement &elem ) const
|
||||
elem.appendChild( elemMaterial );
|
||||
}
|
||||
|
||||
void Polygon3DSymbol::readXml( const QDomElement &elem )
|
||||
void Polygon3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomElement elemDataProperties = elem.firstChildElement( "data" );
|
||||
altClamping = Utils::altClampingFromString( elemDataProperties.attribute( "alt-clamping" ) );
|
||||
altBinding = Utils::altBindingFromString( elemDataProperties.attribute( "alt-binding" ) );
|
||||
@ -56,8 +60,10 @@ Abstract3DSymbol *Point3DSymbol::clone() const
|
||||
return new Point3DSymbol( *this );
|
||||
}
|
||||
|
||||
void Point3DSymbol::writeXml( QDomElement &elem ) const
|
||||
void Point3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomDocument doc = elem.ownerDocument();
|
||||
|
||||
QDomElement elemMaterial = doc.createElement( "material" );
|
||||
@ -73,8 +79,10 @@ void Point3DSymbol::writeXml( QDomElement &elem ) const
|
||||
elem.appendChild( elemTransform );
|
||||
}
|
||||
|
||||
void Point3DSymbol::readXml( const QDomElement &elem )
|
||||
void Point3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomElement elemMaterial = elem.firstChildElement( "material" );
|
||||
material.readXml( elemMaterial );
|
||||
|
||||
@ -103,8 +111,10 @@ Abstract3DSymbol *Line3DSymbol::clone() const
|
||||
return new Line3DSymbol( *this );
|
||||
}
|
||||
|
||||
void Line3DSymbol::writeXml( QDomElement &elem ) const
|
||||
void Line3DSymbol::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomDocument doc = elem.ownerDocument();
|
||||
|
||||
QDomElement elemDataProperties = doc.createElement( "data" );
|
||||
@ -120,8 +130,10 @@ void Line3DSymbol::writeXml( QDomElement &elem ) const
|
||||
elem.appendChild( elemMaterial );
|
||||
}
|
||||
|
||||
void Line3DSymbol::readXml( const QDomElement &elem )
|
||||
void Line3DSymbol::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomElement elemDataProperties = elem.firstChildElement( "data" );
|
||||
altClamping = Utils::altClampingFromString( elemDataProperties.attribute( "alt-clamping" ) );
|
||||
altBinding = Utils::altBindingFromString( elemDataProperties.attribute( "alt-binding" ) );
|
||||
|
@ -16,8 +16,8 @@ class _3D_EXPORT Abstract3DSymbol
|
||||
virtual QString type() const = 0;
|
||||
virtual Abstract3DSymbol *clone() const = 0;
|
||||
|
||||
virtual void writeXml( QDomElement &elem ) const = 0;
|
||||
virtual void readXml( const QDomElement &elem ) = 0;
|
||||
virtual void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const = 0;
|
||||
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -30,8 +30,8 @@ class _3D_EXPORT Polygon3DSymbol : public Abstract3DSymbol
|
||||
QString type() const override { return "polygon"; }
|
||||
Abstract3DSymbol *clone() const override;
|
||||
|
||||
void writeXml( QDomElement &elem ) const override;
|
||||
void readXml( const QDomElement &elem ) override;
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
|
||||
|
||||
AltitudeClamping altClamping; //! how to handle altitude of vector features
|
||||
AltitudeBinding altBinding; //! how to handle clamping of vertices of individual features
|
||||
@ -51,8 +51,8 @@ class _3D_EXPORT Point3DSymbol : public Abstract3DSymbol
|
||||
QString type() const override { return "point"; }
|
||||
Abstract3DSymbol *clone() const override;
|
||||
|
||||
void writeXml( QDomElement &elem ) const override;
|
||||
void readXml( const QDomElement &elem ) override;
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
|
||||
|
||||
PhongMaterialSettings material; //!< Defines appearance of objects
|
||||
QVariantMap shapeProperties; //!< What kind of shape to use and what
|
||||
@ -69,8 +69,8 @@ class _3D_EXPORT Line3DSymbol : public Abstract3DSymbol
|
||||
QString type() const override { return "line"; }
|
||||
Abstract3DSymbol *clone() const override;
|
||||
|
||||
void writeXml( QDomElement &elem ) const override;
|
||||
void readXml( const QDomElement &elem ) override;
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
|
||||
|
||||
AltitudeClamping altClamping; //! how to handle altitude of vector features
|
||||
AltitudeBinding altBinding; //! how to handle clamping of vertices of individual features
|
||||
|
@ -57,8 +57,6 @@ Map3D::~Map3D()
|
||||
|
||||
void Map3D::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
|
||||
QDomElement elemOrigin = elem.firstChildElement( "origin" );
|
||||
originX = elemOrigin.attribute( "x" ).toDouble();
|
||||
originY = elemOrigin.attribute( "y" ).toDouble();
|
||||
@ -117,7 +115,7 @@ void Map3D::readXml( const QDomElement &elem, const QgsReadWriteContext &context
|
||||
|
||||
if ( renderer )
|
||||
{
|
||||
renderer->readXml( elemRenderer );
|
||||
renderer->readXml( elemRenderer, context );
|
||||
renderers.append( renderer );
|
||||
}
|
||||
elemRenderer = elemRenderer.nextSiblingElement( "renderer" );
|
||||
@ -135,7 +133,6 @@ void Map3D::readXml( const QDomElement &elem, const QgsReadWriteContext &context
|
||||
|
||||
QDomElement Map3D::writeXml( QDomDocument &doc, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
Q_UNUSED( context );
|
||||
QDomElement elem = doc.createElement( "qgis3d" );
|
||||
|
||||
QDomElement elemOrigin = doc.createElement( "origin" );
|
||||
@ -171,7 +168,7 @@ QDomElement Map3D::writeXml( QDomDocument &doc, const QgsReadWriteContext &conte
|
||||
{
|
||||
QDomElement elemRenderer = doc.createElement( "renderer" );
|
||||
elemRenderer.setAttribute( "type", renderer->type() );
|
||||
renderer->writeXml( elemRenderer );
|
||||
renderer->writeXml( elemRenderer, context );
|
||||
elemRenderers.appendChild( elemRenderer );
|
||||
}
|
||||
elem.appendChild( elemRenderers );
|
||||
|
@ -9,6 +9,22 @@
|
||||
#include "qgsxmlutils.h"
|
||||
|
||||
|
||||
VectorLayer3DRendererMetadata::VectorLayer3DRendererMetadata()
|
||||
: Qgs3DRendererAbstractMetadata( "vector" )
|
||||
{
|
||||
}
|
||||
|
||||
QgsAbstract3DRenderer *VectorLayer3DRendererMetadata::createRenderer( QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
VectorLayer3DRenderer *r = new VectorLayer3DRenderer;
|
||||
r->readXml( elem, context );
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
// ---------
|
||||
|
||||
|
||||
VectorLayer3DRenderer::VectorLayer3DRenderer( Abstract3DSymbol *s )
|
||||
: mSymbol( s )
|
||||
{
|
||||
@ -62,7 +78,7 @@ Qt3DCore::QEntity *VectorLayer3DRenderer::createEntity( const Map3D &map ) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void VectorLayer3DRenderer::writeXml( QDomElement &elem ) const
|
||||
void VectorLayer3DRenderer::writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const
|
||||
{
|
||||
QDomDocument doc = elem.ownerDocument();
|
||||
|
||||
@ -72,12 +88,12 @@ void VectorLayer3DRenderer::writeXml( QDomElement &elem ) const
|
||||
if ( mSymbol )
|
||||
{
|
||||
elemSymbol.setAttribute( "type", mSymbol->type() );
|
||||
mSymbol->writeXml( elemSymbol );
|
||||
mSymbol->writeXml( elemSymbol, context );
|
||||
}
|
||||
elem.appendChild( elemSymbol );
|
||||
}
|
||||
|
||||
void VectorLayer3DRenderer::readXml( const QDomElement &elem )
|
||||
void VectorLayer3DRenderer::readXml( const QDomElement &elem, const QgsReadWriteContext &context )
|
||||
{
|
||||
layerRef = QgsMapLayerRef( elem.attribute( "layer" ) );
|
||||
|
||||
@ -92,7 +108,7 @@ void VectorLayer3DRenderer::readXml( const QDomElement &elem )
|
||||
symbol = new Line3DSymbol;
|
||||
|
||||
if ( symbol )
|
||||
symbol->readXml( elemSymbol );
|
||||
symbol->readXml( elemSymbol, context );
|
||||
mSymbol.reset( symbol );
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "qgis_3d.h"
|
||||
|
||||
#include "qgs3drendererregistry.h"
|
||||
#include "qgsabstract3drenderer.h"
|
||||
|
||||
#include "phongmaterialsettings.h"
|
||||
@ -16,6 +17,17 @@ class QgsVectorLayer;
|
||||
|
||||
class Abstract3DSymbol;
|
||||
|
||||
|
||||
//! Metadata for vector layer 3D renderer to allow creation of its instances from XML
|
||||
class _3D_EXPORT VectorLayer3DRendererMetadata : public Qgs3DRendererAbstractMetadata
|
||||
{
|
||||
public:
|
||||
VectorLayer3DRendererMetadata();
|
||||
|
||||
virtual QgsAbstract3DRenderer *createRenderer( QDomElement &elem, const QgsReadWriteContext &context ) override;
|
||||
};
|
||||
|
||||
|
||||
/** 3D renderer that renders all features of a vector layer with the same 3D symbol.
|
||||
* The appearance is completely defined by the symbol.
|
||||
*/
|
||||
@ -37,8 +49,8 @@ class _3D_EXPORT VectorLayer3DRenderer : public QgsAbstract3DRenderer
|
||||
VectorLayer3DRenderer *clone() const override;
|
||||
Qt3DCore::QEntity *createEntity( const Map3D &map ) const override;
|
||||
|
||||
void writeXml( QDomElement &elem ) const override;
|
||||
void readXml( const QDomElement &elem ) override;
|
||||
void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const override;
|
||||
void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) override;
|
||||
void resolveReferences( const QgsProject &project ) override;
|
||||
|
||||
private:
|
||||
|
@ -80,6 +80,15 @@
|
||||
#include "qgsziputils.h"
|
||||
#include "qgsbrowsermodel.h"
|
||||
|
||||
#ifdef HAVE_3D
|
||||
#include "qgsabstract3drenderer.h"
|
||||
#include "qgs3dmapcanvasdockwidget.h"
|
||||
#include "qgs3drendererregistry.h"
|
||||
#include "map3d.h"
|
||||
#include "flatterraingenerator.h"
|
||||
#include "vectorlayer3drenderer.h"
|
||||
#endif
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkProxy>
|
||||
#include <QAuthenticator>
|
||||
@ -800,6 +809,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipVersionCh
|
||||
functionProfile( &QgisApp::updateRecentProjectPaths, this, QStringLiteral( "Update recent project paths" ) );
|
||||
functionProfile( &QgisApp::updateProjectFromTemplates, this, QStringLiteral( "Update project from templates" ) );
|
||||
functionProfile( &QgisApp::legendLayerSelectionChanged, this, QStringLiteral( "Legend layer selection changed" ) );
|
||||
functionProfile( &QgisApp::init3D, this, QStringLiteral( "Initialize 3D support" ) );
|
||||
|
||||
QgsApplication::annotationRegistry()->addAnnotationType( QgsAnnotationMetadata( QStringLiteral( "FormAnnotationItem" ), &QgsFormAnnotation::create ) );
|
||||
connect( QgsProject::instance()->annotationManager(), &QgsAnnotationManager::annotationAdded, this, &QgisApp::annotationCreated );
|
||||
@ -9879,13 +9889,19 @@ void QgisApp::newMapCanvas()
|
||||
}
|
||||
}
|
||||
|
||||
#include "qgsabstract3drenderer.h"
|
||||
#include "qgs3dmapcanvasdockwidget.h"
|
||||
#include "map3d.h"
|
||||
#include "flatterraingenerator.h"
|
||||
void QgisApp::init3D()
|
||||
{
|
||||
#ifdef HAVE_3D
|
||||
// register 3D renderers
|
||||
QgsApplication::instance()->renderer3DRegistry()->addRenderer( new VectorLayer3DRendererMetadata );
|
||||
#else
|
||||
mActionNew3DMapCanvas->setVisible( false );
|
||||
#endif
|
||||
}
|
||||
|
||||
void QgisApp::new3DMapCanvas()
|
||||
{
|
||||
#ifdef HAVE_3D
|
||||
// initialize from project
|
||||
QgsProject *prj = QgsProject::instance();
|
||||
QgsRectangle fullExtent = mMapCanvas->fullExtent();
|
||||
@ -9910,6 +9926,7 @@ void QgisApp::new3DMapCanvas()
|
||||
map3DWidget->setMap( map );
|
||||
map3DWidget->setMainCanvas( mMapCanvas );
|
||||
addDockWidget( Qt::BottomDockWidgetArea, map3DWidget );
|
||||
#endif
|
||||
}
|
||||
|
||||
void QgisApp::setExtent( const QgsRectangle &rect )
|
||||
|
@ -1700,6 +1700,7 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
|
||||
void createCanvasTools();
|
||||
void createMapTips();
|
||||
void createDecorations();
|
||||
void init3D();
|
||||
|
||||
/**
|
||||
* Refresh the user profile menu.
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
class QDomElement;
|
||||
class QgsProject;
|
||||
class QgsReadWriteContext;
|
||||
class Map3D;
|
||||
|
||||
namespace Qt3DCore
|
||||
@ -25,8 +26,8 @@ class CORE_EXPORT QgsAbstract3DRenderer //: public QObject
|
||||
virtual QgsAbstract3DRenderer *clone() const = 0;
|
||||
virtual Qt3DCore::QEntity *createEntity( const Map3D &map ) const = 0;
|
||||
|
||||
virtual void writeXml( QDomElement &elem ) const = 0;
|
||||
virtual void readXml( const QDomElement &elem ) = 0;
|
||||
virtual void writeXml( QDomElement &elem, const QgsReadWriteContext &context ) const = 0;
|
||||
virtual void readXml( const QDomElement &elem, const QgsReadWriteContext &context ) = 0;
|
||||
virtual void resolveReferences( const QgsProject &project );
|
||||
};
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include "qgs3drendererregistry.h"
|
||||
#include "qgsabstract3drenderer.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgscoordinatereferencesystem.h"
|
||||
@ -550,6 +551,19 @@ bool QgsMapLayer::readLayerXml( const QDomElement &layerElement, const QgsReadWr
|
||||
QDomElement metadataElem = layerElement.firstChildElement( QStringLiteral( "resourceMetadata" ) );
|
||||
mMetadata.readMetadataXml( metadataElem );
|
||||
|
||||
QgsAbstract3DRenderer *r3D = nullptr;
|
||||
QDomElement renderer3DElem = layerElement.firstChildElement( QStringLiteral( "renderer-3d" ) );
|
||||
if ( !renderer3DElem.isNull() )
|
||||
{
|
||||
QString type3D = renderer3DElem.attribute( QStringLiteral( "type" ) );
|
||||
Qgs3DRendererAbstractMetadata *meta3D = QgsApplication::renderer3DRegistry()->rendererMetadata( type3D );
|
||||
if ( meta3D )
|
||||
{
|
||||
r3D = meta3D->createRenderer( renderer3DElem, context );
|
||||
}
|
||||
}
|
||||
setRenderer3D( r3D );
|
||||
|
||||
return true;
|
||||
} // bool QgsMapLayer::readLayerXML
|
||||
|
||||
@ -819,6 +833,14 @@ bool QgsMapLayer::writeLayerXml( QDomElement &layerElement, QDomDocument &docume
|
||||
mMetadata.writeMetadataXml( myMetadataElem, document );
|
||||
layerElement.appendChild( myMetadataElem );
|
||||
|
||||
if ( m3DRenderer )
|
||||
{
|
||||
QDomElement renderer3DElem = document.createElement( QStringLiteral( "renderer-3d" ) );
|
||||
renderer3DElem.setAttribute( QStringLiteral( "type" ), m3DRenderer->type() );
|
||||
m3DRenderer->writeXml( renderer3DElem, context );
|
||||
layerElement.appendChild( renderer3DElem );
|
||||
}
|
||||
|
||||
// now append layer node to map layer node
|
||||
|
||||
writeCustomProperties( layerElement, document );
|
||||
@ -838,6 +860,12 @@ bool QgsMapLayer::writeXml( QDomNode &layer_node, QDomDocument &document, const
|
||||
return true;
|
||||
} // void QgsMapLayer::writeXml
|
||||
|
||||
void QgsMapLayer::resolveReferences( QgsProject *project )
|
||||
{
|
||||
if ( m3DRenderer )
|
||||
m3DRenderer->resolveReferences( *project );
|
||||
}
|
||||
|
||||
|
||||
void QgsMapLayer::readCustomProperties( const QDomNode &layerNode, const QString &keyStartsWith )
|
||||
{
|
||||
|
@ -431,6 +431,11 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
*/
|
||||
bool writeLayerXml( QDomElement &layerElement, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
/** Resolve references to other layers (kept as layer IDs after reading XML) into layer objects.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
virtual void resolveReferences( QgsProject *project );
|
||||
|
||||
/** Returns list of all keys within custom properties. Properties are stored in a map and saved in project file.
|
||||
* \see customProperty()
|
||||
* \since QGIS 3.0
|
||||
@ -976,7 +981,6 @@ class CORE_EXPORT QgsMapLayer : public QObject
|
||||
*/
|
||||
virtual bool writeXml( QDomNode &layer_node, QDomDocument &document, const QgsReadWriteContext &context ) const;
|
||||
|
||||
|
||||
/** Read custom properties from project file.
|
||||
\param layerNode note to read from
|
||||
\param keyStartsWith reads only properties starting with the specified string (or all if the string is empty)*/
|
||||
|
@ -942,13 +942,12 @@ bool QgsProject::readProjectFile( const QString &filename )
|
||||
mBadLayerHandler->handleBadLayers( brokenNodes );
|
||||
}
|
||||
|
||||
// Resolve references to other vector layers
|
||||
// Resolve references to other layers
|
||||
// Needs to be done here once all dependent layers are loaded
|
||||
QMap<QString, QgsMapLayer *> layers = mLayerStore->mapLayers();
|
||||
for ( QMap<QString, QgsMapLayer *>::iterator it = layers.begin(); it != layers.end(); it++ )
|
||||
{
|
||||
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( it.value() ) )
|
||||
vl->resolveReferences( this );
|
||||
it.value()->resolveReferences( this );
|
||||
}
|
||||
|
||||
mLayerTreeRegistryBridge->setEnabled( true );
|
||||
|
@ -1654,6 +1654,7 @@ bool QgsVectorLayer::writeXml( QDomNode &layer_node,
|
||||
|
||||
void QgsVectorLayer::resolveReferences( QgsProject *project )
|
||||
{
|
||||
QgsMapLayer::resolveReferences( project );
|
||||
mJoinBuffer->resolveReferences( project );
|
||||
}
|
||||
|
||||
|
@ -709,7 +709,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
/** Resolve references to other layers (kept as layer IDs after reading XML) into layer objects.
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
void resolveReferences( QgsProject *project );
|
||||
virtual void resolveReferences( QgsProject *project ) override;
|
||||
|
||||
/**
|
||||
* Save named and sld style of the layer to the style table in the db.
|
||||
|
Loading…
x
Reference in New Issue
Block a user