QGIS API Documentation 3.34.0-Prizren (ffbdd678812)
Loading...
Searching...
No Matches
qgsgltf3dutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgltf3dutils.cpp
3 --------------------------------------
4 Date : July 2023
5 Copyright : (C) 2023 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16
17#include "qgsgltf3dutils.h"
18
19#include "qgsgltfutils.h"
22#include "qgslogger.h"
23
24#include <Qt3DCore/QEntity>
25
26#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
27#include <Qt3DRender/QAttribute>
28#include <Qt3DRender/QBuffer>
29#include <Qt3DRender/QGeometry>
30typedef Qt3DRender::QAttribute Qt3DQAttribute;
31typedef Qt3DRender::QBuffer Qt3DQBuffer;
32typedef Qt3DRender::QGeometry Qt3DQGeometry;
33#else
34#include <Qt3DCore/QAttribute>
35#include <Qt3DCore/QBuffer>
36#include <Qt3DCore/QGeometry>
37typedef Qt3DCore::QAttribute Qt3DQAttribute;
38typedef Qt3DCore::QBuffer Qt3DQBuffer;
39typedef Qt3DCore::QGeometry Qt3DQGeometry;
40#endif
41
42#include <Qt3DRender/QGeometryRenderer>
43#include <Qt3DRender/QTexture>
44#include <Qt3DExtras/QMetalRoughMaterial>
45#include <Qt3DExtras/QTextureMaterial>
46
47#include <QFile>
48#include <QFileInfo>
49#include <QMatrix4x4>
50
51#define TINYGLTF_NO_STB_IMAGE // we use QImage-based reading of images
52#define TINYGLTF_NO_STB_IMAGE_WRITE // we don't need writing of images
53#include "tiny_gltf.h"
54
55
57
58static Qt3DQAttribute::VertexBaseType parseVertexBaseType( int componentType )
59{
60 switch ( componentType )
61 {
62 case TINYGLTF_COMPONENT_TYPE_BYTE:
63 return Qt3DQAttribute::Byte;
64 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE:
65 return Qt3DQAttribute::UnsignedByte;
66 case TINYGLTF_COMPONENT_TYPE_SHORT:
67 return Qt3DQAttribute::Short;
68 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT:
69 return Qt3DQAttribute::UnsignedShort;
70 case TINYGLTF_COMPONENT_TYPE_INT:
71 return Qt3DQAttribute::Int;
72 case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT:
73 return Qt3DQAttribute::UnsignedInt;
74 case TINYGLTF_COMPONENT_TYPE_FLOAT:
75 return Qt3DQAttribute::Float;
76 case TINYGLTF_COMPONENT_TYPE_DOUBLE:
77 return Qt3DQAttribute::Double;
78 }
79 Q_ASSERT( false );
80 return Qt3DQAttribute::UnsignedInt;
81}
82
83
84static Qt3DRender::QAbstractTexture::Filter parseTextureFilter( int filter )
85{
86 switch ( filter )
87 {
88 case TINYGLTF_TEXTURE_FILTER_NEAREST:
89 return Qt3DRender::QTexture2D::Nearest;
90 case TINYGLTF_TEXTURE_FILTER_LINEAR:
91 return Qt3DRender::QTexture2D::Linear;
92 case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_NEAREST:
93 return Qt3DRender::QTexture2D::NearestMipMapNearest;
94 case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST:
95 return Qt3DRender::QTexture2D::LinearMipMapNearest;
96 case TINYGLTF_TEXTURE_FILTER_NEAREST_MIPMAP_LINEAR:
97 return Qt3DRender::QTexture2D::NearestMipMapLinear;
98 case TINYGLTF_TEXTURE_FILTER_LINEAR_MIPMAP_LINEAR:
99 return Qt3DRender::QTexture2D::LinearMipMapLinear;
100 }
101
102 // play it safe and handle malformed models
103 return Qt3DRender::QTexture2D::Nearest;
104}
105
106static Qt3DRender::QTextureWrapMode::WrapMode parseTextureWrapMode( int wrapMode )
107{
108 switch ( wrapMode )
109 {
110 case TINYGLTF_TEXTURE_WRAP_REPEAT:
111 return Qt3DRender::QTextureWrapMode::Repeat;
112 case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE:
113 return Qt3DRender::QTextureWrapMode::ClampToEdge;
114 case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT:
115 return Qt3DRender::QTextureWrapMode::MirroredRepeat;
116 }
117 // some malformed GLTF models have incorrect texture wrap modes (eg
118 // https://qld.digitaltwin.terria.io/api/v0/data/b73ccb60-66ef-4470-8c3c-44af36c4d69b/CBD/tileset.json )
119 return Qt3DRender::QTextureWrapMode::Repeat;
120}
121
122
123static Qt3DQAttribute *parseAttribute( tinygltf::Model &model, int accessorIndex )
124{
125 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
126 tinygltf::BufferView &bv = model.bufferViews[accessor.bufferView];
127 tinygltf::Buffer &b = model.buffers[bv.buffer];
128
129 // TODO: only ever create one QBuffer for a buffer even if it is used multiple times
130 QByteArray byteArray( reinterpret_cast<const char *>( b.data.data() ),
131 static_cast<int>( b.data.size() ) ); // makes a deep copy
132 Qt3DQBuffer *buffer = new Qt3DQBuffer();
133 buffer->setData( byteArray );
134
135 Qt3DQAttribute *attribute = new Qt3DQAttribute();
136
137 // "target" is optional, can be zero
138 if ( bv.target == TINYGLTF_TARGET_ARRAY_BUFFER )
139 attribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
140 else if ( bv.target == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER )
141 attribute->setAttributeType( Qt3DQAttribute::IndexAttribute );
142
143 attribute->setBuffer( buffer );
144 attribute->setByteOffset( bv.byteOffset + accessor.byteOffset );
145 attribute->setByteStride( bv.byteStride ); // could be zero, it seems that's fine (assuming packed)
146 attribute->setCount( accessor.count );
147 attribute->setVertexBaseType( parseVertexBaseType( accessor.componentType ) );
148 attribute->setVertexSize( tinygltf::GetNumComponentsInType( accessor.type ) );
149
150 return attribute;
151}
152
153
154static Qt3DQAttribute *reprojectPositions( tinygltf::Model &model, int accessorIndex, const QgsGltf3DUtils::EntityTransform &transform, const QgsVector3D &tileTranslationEcef, QMatrix4x4 *matrix )
155{
156 tinygltf::Accessor &accessor = model.accessors[accessorIndex];
157
158 QVector<double> vx, vy, vz;
159 bool res = QgsGltfUtils::accessorToMapCoordinates( model, accessorIndex, transform.tileTransform, transform.ecefToTargetCrs, tileTranslationEcef, matrix, transform.gltfUpAxis, vx, vy, vz );
160 if ( !res )
161 return nullptr;
162
163 QByteArray byteArray;
164 byteArray.resize( accessor.count * 4 * 3 );
165 float *out = reinterpret_cast<float *>( byteArray.data() );
166
167 QgsVector3D sceneOrigin = transform.sceneOriginTargetCrs;
168 for ( int i = 0; i < static_cast<int>( accessor.count ); ++i )
169 {
170 double x = vx[i] - sceneOrigin.x();
171 double y = vy[i] - sceneOrigin.y();
172 double z = ( vz[i] * transform.zValueScale ) + transform.zValueOffset - sceneOrigin.z();
173
174 // QGIS 3D uses base plane (X,-Z) with Y up - so flip the coordinates
175 out[i * 3 + 0] = static_cast< float >( x );
176 out[i * 3 + 1] = static_cast< float >( z );
177 out[i * 3 + 2] = static_cast< float >( -y );
178 }
179
180 Qt3DQBuffer *buffer = new Qt3DQBuffer();
181 buffer->setData( byteArray );
182
183 Qt3DQAttribute *attribute = new Qt3DQAttribute();
184 attribute->setAttributeType( Qt3DQAttribute::VertexAttribute );
185 attribute->setBuffer( buffer );
186 attribute->setByteOffset( 0 );
187 attribute->setByteStride( 12 );
188 attribute->setCount( accessor.count );
189 attribute->setVertexBaseType( Qt3DQAttribute::Float );
190 attribute->setVertexSize( 3 );
191
192 return attribute;
193}
194
195
196// QAbstractFunctor marked as deprecated in 5.15, but undeprecated for Qt 6.0. TODO -- remove when we require 6.0
198
199class TinyGltfTextureImageDataGenerator : public Qt3DRender::QTextureImageDataGenerator
200{
201 public:
202 TinyGltfTextureImageDataGenerator( Qt3DRender::QTextureImageDataPtr imagePtr )
203 : mImagePtr( imagePtr ) {}
204
205 QT3D_FUNCTOR( TinyGltfTextureImageDataGenerator )
206
207 Qt3DRender::QTextureImageDataPtr operator()() override
208 {
209 return mImagePtr;
210 }
211
212 bool operator ==( const QTextureImageDataGenerator &other ) const override
213 {
214 const TinyGltfTextureImageDataGenerator *otherFunctor = functor_cast<TinyGltfTextureImageDataGenerator>( &other );
215 return mImagePtr.get() == otherFunctor->mImagePtr.get();
216 }
217
218 Qt3DRender::QTextureImageDataPtr mImagePtr;
219};
220
222
223class TinyGltfTextureImage : public Qt3DRender::QAbstractTextureImage
224{
225 Q_OBJECT
226 public:
227 TinyGltfTextureImage( tinygltf::Image &image )
228 {
229 Q_ASSERT( image.bits == 8 );
230 Q_ASSERT( image.component == 4 );
231 Q_ASSERT( image.pixel_type == TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE );
232
233 imgDataPtr.reset( new Qt3DRender::QTextureImageData );
234 imgDataPtr->setWidth( image.width );
235 imgDataPtr->setHeight( image.height );
236 imgDataPtr->setDepth( 1 ); // not sure what this is
237 imgDataPtr->setFaces( 1 );
238 imgDataPtr->setLayers( 1 );
239 imgDataPtr->setMipLevels( 1 );
240 QByteArray imageBytes( reinterpret_cast<const char *>( image.image.data() ), image.image.size() );
241 imgDataPtr->setData( imageBytes, 4 );
242 imgDataPtr->setFormat( QOpenGLTexture::RGBA8_UNorm );
243 imgDataPtr->setPixelFormat( QOpenGLTexture::BGRA ); // when using tinygltf with STB_image, pixel format is QOpenGLTexture::RGBA
244 imgDataPtr->setPixelType( QOpenGLTexture::UInt8 );
245 imgDataPtr->setTarget( QOpenGLTexture::Target2D );
246 }
247
248 Qt3DRender::QTextureImageDataGeneratorPtr dataGenerator() const override
249 {
250 return Qt3DRender::QTextureImageDataGeneratorPtr( new TinyGltfTextureImageDataGenerator( imgDataPtr ) );
251 }
252
253 Qt3DRender::QTextureImageDataPtr imgDataPtr;
254};
255
256
257// TODO: move elsewhere
258static QByteArray fetchUri( const QUrl &url, QStringList *errors )
259{
260 if ( url.scheme().startsWith( "http" ) )
261 {
262 QNetworkRequest request = QNetworkRequest( url );
263 request.setAttribute( QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache );
264 request.setAttribute( QNetworkRequest::CacheSaveControlAttribute, true );
265 QgsBlockingNetworkRequest networkRequest;
266 // TODO: setup auth, setup headers
267 if ( networkRequest.get( request ) != QgsBlockingNetworkRequest::NoError )
268 {
269 if ( errors )
270 *errors << QStringLiteral( "Failed to download image: %1" ).arg( url.toString() );
271 }
272 else
273 {
274 const QgsNetworkReplyContent content = networkRequest.reply();
275 return content.content();
276 }
277 }
278 else if ( url.isLocalFile() && QFile::exists( url.toLocalFile() ) )
279 {
280 QFile f( url.toLocalFile() );
281 if ( f.open( QIODevice::ReadOnly ) )
282 {
283 return f.readAll();
284 }
285 else
286 {
287 if ( errors )
288 *errors << QStringLiteral( "Unable to open image: %1" ).arg( url.toString() );
289 }
290 }
291 return QByteArray();
292}
293
294
295static Qt3DRender::QMaterial *parseMaterial( tinygltf::Model &model, int materialIndex, QString baseUri, QStringList *errors )
296{
297 if ( materialIndex < 0 )
298 {
299 // material unspecified - using default
300 Qt3DExtras::QMetalRoughMaterial *defaultMaterial = new Qt3DExtras::QMetalRoughMaterial;
301 defaultMaterial->setMetalness( 1 );
302 defaultMaterial->setRoughness( 1 );
303 defaultMaterial->setBaseColor( QColor::fromRgbF( 1, 1, 1 ) );
304 return defaultMaterial;
305 }
306
307 tinygltf::Material &material = model.materials[materialIndex];
308 tinygltf::PbrMetallicRoughness &pbr = material.pbrMetallicRoughness;
309
310 if ( pbr.baseColorTexture.index >= 0 )
311 {
312 tinygltf::Texture &tex = model.textures[pbr.baseColorTexture.index];
313
314 tinygltf::Image &img = model.images[tex.source];
315
316 if ( !img.uri.empty() )
317 {
318 QString imgUri = QString::fromStdString( img.uri );
319 QUrl url = QUrl( baseUri ).resolved( imgUri );
320 QByteArray ba = fetchUri( url, errors );
321 if ( !ba.isEmpty() )
322 {
323 if ( !QgsGltfUtils::loadImageDataWithQImage( &img, -1, nullptr, nullptr, 0, 0, ( const unsigned char * ) ba.constData(), ba.size(), nullptr ) )
324 {
325 if ( errors )
326 *errors << QStringLiteral( "Failed to load image: %1" ).arg( imgUri );
327 }
328 }
329 }
330
331 if ( img.image.empty() )
332 {
333 Qt3DExtras::QMetalRoughMaterial *pbrMaterial = new Qt3DExtras::QMetalRoughMaterial;
334 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
335 pbrMaterial->setRoughness( pbr.roughnessFactor );
336 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
337 return pbrMaterial;
338 }
339
340 TinyGltfTextureImage *textureImage = new TinyGltfTextureImage( img );
341
342 Qt3DRender::QTexture2D *texture = new Qt3DRender::QTexture2D;
343 texture->addTextureImage( textureImage ); // textures take the ownership of textureImage if has no parant
344
345 // let's use linear (rather than nearest) filtering by default to avoid blocky look of textures
346 texture->setMinificationFilter( Qt3DRender::QTexture2D::Linear );
347 texture->setMagnificationFilter( Qt3DRender::QTexture2D::Linear );
348
349 if ( tex.sampler >= 0 )
350 {
351 tinygltf::Sampler &sampler = model.samplers[tex.sampler];
352 if ( sampler.minFilter >= 0 )
353 texture->setMinificationFilter( parseTextureFilter( sampler.minFilter ) );
354 if ( sampler.magFilter >= 0 )
355 texture->setMagnificationFilter( parseTextureFilter( sampler.magFilter ) );
356 Qt3DRender::QTextureWrapMode wrapMode;
357 wrapMode.setX( parseTextureWrapMode( sampler.wrapS ) );
358 wrapMode.setY( parseTextureWrapMode( sampler.wrapT ) );
359 texture->setWrapMode( wrapMode );
360 }
361
362 // We should be using PBR material unless unlit material is requested using KHR_materials_unlit
363 // GLTF extension, but in various datasets that extension is not used (even though it should have been).
364 // In the future we may want to have a switch whether to use unlit material or PBR material...
365 Qt3DExtras::QTextureMaterial *mat = new Qt3DExtras::QTextureMaterial;
366 mat->setTexture( texture );
367 return mat;
368 }
369
370 Qt3DExtras::QMetalRoughMaterial *pbrMaterial = new Qt3DExtras::QMetalRoughMaterial;
371 pbrMaterial->setMetalness( pbr.metallicFactor ); // [0..1] or texture
372 pbrMaterial->setRoughness( pbr.roughnessFactor );
373 pbrMaterial->setBaseColor( QColor::fromRgbF( pbr.baseColorFactor[0], pbr.baseColorFactor[1], pbr.baseColorFactor[2], pbr.baseColorFactor[3] ) );
374 return pbrMaterial;
375}
376
377
378static QVector<Qt3DCore::QEntity *> parseNode( tinygltf::Model &model, int nodeIndex, const QgsGltf3DUtils::EntityTransform &transform, const QgsVector3D &tileTranslationEcef, QString baseUri, QMatrix4x4 parentTransform, QStringList *errors )
379{
380 tinygltf::Node &node = model.nodes[nodeIndex];
381
382 QVector<Qt3DCore::QEntity *> entities;
383
384 // transform
385 std::unique_ptr<QMatrix4x4> matrix = QgsGltfUtils::parseNodeTransform( node );
386 if ( !parentTransform.isIdentity() )
387 {
388 if ( matrix )
389 *matrix = parentTransform * *matrix;
390 else
391 {
392 matrix.reset( new QMatrix4x4( parentTransform ) );
393 }
394 }
395
396 // mesh
397 if ( node.mesh >= 0 )
398 {
399 tinygltf::Mesh &mesh = model.meshes[node.mesh];
400
401 for ( const tinygltf::Primitive &primitive : mesh.primitives )
402 {
403 if ( primitive.mode != TINYGLTF_MODE_TRIANGLES )
404 {
405 if ( errors )
406 *errors << QStringLiteral( "Unsupported mesh primitive: %1" ).arg( primitive.mode );
407 continue;
408 }
409
410 auto posIt = primitive.attributes.find( "POSITION" );
411 Q_ASSERT( posIt != primitive.attributes.end() );
412 int positionAccessorIndex = posIt->second;
413
414 tinygltf::Accessor &posAccessor = model.accessors[positionAccessorIndex];
415 if ( posAccessor.componentType != TINYGLTF_PARAMETER_TYPE_FLOAT || posAccessor.type != TINYGLTF_TYPE_VEC3 )
416 {
417 if ( errors )
418 *errors << QStringLiteral( "Unsupported position accessor type: %1 / %2" ).arg( posAccessor.componentType ).arg( posAccessor.type );
419 continue;
420 }
421
422 Qt3DQGeometry *geom = new Qt3DQGeometry;
423
424 Qt3DQAttribute *positionAttribute = reprojectPositions( model, positionAccessorIndex, transform, tileTranslationEcef, matrix.get() );
425 positionAttribute->setName( Qt3DQAttribute::defaultPositionAttributeName() );
426 geom->addAttribute( positionAttribute );
427
428 auto normalIt = primitive.attributes.find( "NORMAL" );
429 if ( normalIt != primitive.attributes.end() )
430 {
431 int normalAccessorIndex = normalIt->second;
432 Qt3DQAttribute *normalAttribute = parseAttribute( model, normalAccessorIndex );
433 normalAttribute->setName( Qt3DQAttribute::defaultNormalAttributeName() );
434 geom->addAttribute( normalAttribute );
435
436 // TODO: we may need to transform normal vectors when we are altering positions
437 // (but quite often normals are actually note needed - e.g. when using textured data)
438 }
439
440 auto texIt = primitive.attributes.find( "TEXCOORD_0" );
441 if ( texIt != primitive.attributes.end() )
442 {
443 int texAccessorIndex = texIt->second;
444 Qt3DQAttribute *texAttribute = parseAttribute( model, texAccessorIndex );
445 texAttribute->setName( Qt3DQAttribute::defaultTextureCoordinateAttributeName() );
446 geom->addAttribute( texAttribute );
447 }
448
449 Qt3DQAttribute *indexAttribute = nullptr;
450 if ( primitive.indices != -1 )
451 {
452 indexAttribute = parseAttribute( model, primitive.indices );
453 geom->addAttribute( indexAttribute );
454 }
455
456 Qt3DRender::QGeometryRenderer *geomRenderer = new Qt3DRender::QGeometryRenderer;
457 geomRenderer->setGeometry( geom );
458 geomRenderer->setPrimitiveType( Qt3DRender::QGeometryRenderer::Triangles ); // looks like same values as "mode"
459 geomRenderer->setVertexCount( indexAttribute ? indexAttribute->count() : model.accessors[positionAccessorIndex].count );
460
461 Qt3DRender::QMaterial *material = parseMaterial( model, primitive.material, baseUri, errors );
462
463 // TODO: if we are using PBR material, and normal vectors are not present in the data,
464 // they should be auto-generated by us (according to GLTF spec)
465
466 Qt3DCore::QEntity *primitiveEntity = new Qt3DCore::QEntity;
467 primitiveEntity->addComponent( geomRenderer );
468 primitiveEntity->addComponent( material );
469 entities << primitiveEntity;
470 }
471 }
472
473 // recursively add children
474 for ( int childNodeIndex : node.children )
475 {
476 entities << parseNode( model, childNodeIndex, transform, tileTranslationEcef, baseUri, matrix ? *matrix : QMatrix4x4(), errors );
477 }
478
479 return entities;
480}
481
482
483static Qt3DCore::QEntity *parseModel( tinygltf::Model &model, const QgsGltf3DUtils::EntityTransform &transform, QString baseUri, QStringList *errors )
484{
485 tinygltf::Scene &scene = model.scenes[model.defaultScene];
486
487 if ( scene.nodes.size() == 0 )
488 {
489 if ( errors )
490 *errors << "No nodes present in the gltf data!";
491 return nullptr;
492 }
493
494 const QgsVector3D tileTranslationEcef = QgsGltfUtils::extractTileTranslation( model );
495
496 Qt3DCore::QEntity *rootEntity = new Qt3DCore::QEntity;
497 for ( const int nodeIndex : scene.nodes )
498 {
499 const QVector<Qt3DCore::QEntity *> entities = parseNode( model, nodeIndex, transform, tileTranslationEcef, baseUri, QMatrix4x4(), errors );
500 for ( Qt3DCore::QEntity *e : entities )
501 e->setParent( rootEntity );
502 }
503 return rootEntity;
504}
505
506
507Qt3DCore::QEntity *QgsGltf3DUtils::gltfToEntity( const QByteArray &data, const QgsGltf3DUtils::EntityTransform &transform, const QString &baseUri, QStringList *errors )
508{
509 tinygltf::Model model;
510 QString gltfErrors, gltfWarnings;
511
512 bool res = QgsGltfUtils::loadGltfModel( data, model, &gltfErrors, &gltfWarnings );
513 if ( !gltfErrors.isEmpty() )
514 {
515 QgsDebugError( QStringLiteral( "Error raised reading %1: %2" ).arg( baseUri, gltfErrors ) );
516 }
517 if ( !gltfWarnings.isEmpty() )
518 {
519 QgsDebugError( QStringLiteral( "Warnings raised reading %1: %2" ).arg( baseUri, gltfWarnings ) );
520 }
521 if ( !res )
522 {
523 if ( errors )
524 {
525 errors->append( QStringLiteral( "GLTF load error: " ) + gltfErrors );
526 }
527 return nullptr;
528 }
529
530 return parseModel( model, transform, baseUri, errors );
531}
532
533// For TinyGltfTextureImage
534#include "qgsgltf3dutils.moc"
535
A thread safe class for performing blocking (sync) network requests, with full support for QGIS proxy...
ErrorCode get(QNetworkRequest &request, bool forceRefresh=false, QgsFeedback *feedback=nullptr)
Performs a "get" operation on the specified request.
@ NoError
No error was encountered.
QgsNetworkReplyContent reply() const
Returns the content of the network reply, after a get(), post(), head() or put() request has been mad...
Encapsulates a network reply within a container which is inexpensive to copy and safe to pass between...
QByteArray content() const
Returns the reply content.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:32
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:51
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:53
double x() const
Returns X coordinate.
Definition qgsvector3d.h:49
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:4916
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:4915
Qt3DCore::QAttribute Qt3DQAttribute
Definition qgs3daxis.cpp:28
Qt3DCore::QBuffer Qt3DQBuffer
Definition qgs3daxis.cpp:30
Qt3DCore::QGeometry Qt3DQGeometry
Definition qgs3daxis.cpp:29
bool operator==(const QgsFeatureIterator &fi1, const QgsFeatureIterator &fi2)
Qt3DCore::QAttribute Qt3DQAttribute
Qt3DCore::QBuffer Qt3DQBuffer
Qt3DCore::QGeometry Qt3DQGeometry
#define QgsDebugError(str)
Definition qgslogger.h:38