diff --git a/.github/workflows/windows-qt6.yml b/.github/workflows/windows-qt6.yml index 6d615d06f84..8b4d5a746f8 100644 --- a/.github/workflows/windows-qt6.yml +++ b/.github/workflows/windows-qt6.yml @@ -76,8 +76,6 @@ jobs: -D FLEX_EXECUTABLE="${SOURCE_DIR}/win_flex.exe" \ -D BISON_EXECUTABLE="${SOURCE_DIR}/win_bison.exe" \ -D SIP_BUILD_EXECUTABLE="${BUILD_DIR}\vcpkg_installed\x64-windows-release\tools\python3\Scripts\sip-build.exe" \ - -D PYUIC_PROGRAM="${BUILD_DIR}\vcpkg_installed\x64-windows-release\tools\python3\pyuic5.bat" \ - -D PYRCC_PROGRAM="${BUILD_DIR}\vcpkg_installed\x64-windows-release\tools\python3\pyrcc5.bat" \ -D CMAKE_C_COMPILER_LAUNCHER=ccache \ -D CMAKE_CXX_COMPILER_LAUNCHER=ccache \ -D WITH_QTWEBKIT=OFF \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 164cd524b35..a614db65905 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,18 +26,19 @@ set (WITH_DESKTOP TRUE CACHE BOOL "Determines whether QGIS desktop should be bui set (WITH_GUI TRUE CACHE BOOL "Determines whether QGIS GUI library should be built") set(WITH_VCPKG FALSE CACHE BOOL "Use the vcpkg submodule for dependency management.") -set(WITH_VCPKG TRUE CACHE BOOL "Use the vcpkg submodule for dependency management.") set(SDK_PATH "" CACHE STRING "Build with VCPKG SDK") if(NOT SDK_PATH STREQUAL "") message(STATUS "Building with SDK -- ${SDK_PATH}") set(CMAKE_TOOLCHAIN_FILE "${SDK_PATH}/scripts/buildsystems/vcpkg.cmake") + set(VCPKG_INSTALL_PREFIX "${SDK_PATH}/installed") if(APPLE AND NOT VCPKG_TARGET_TRIPLET) message(FATAL_ERROR "VCPKG_TARGET_TRIPLET not set (set it to arm64-osx-dynamic-release or x64-osx-dynamic-release") endif() set(WITH_VCPKG ON) elseif(WITH_VCPKG) message(STATUS "Building local dependencies with VCPKG --") + set(VCPKG_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/vcpkg_installed") include(VcpkgToolchain) else() message(STATUS "Building with system libraries --") @@ -49,6 +50,9 @@ if(WITH_VCPKG) else() list(APPEND CMAKE_PROGRAM_PATH "${VCPKG_INSTALL_PREFIX}/${VCPKG_TARGET_TRIPLET}/bin") endif() + set(PREFER_INTERNAL_LIBS FALSE) +else() + set(PREFER_INTERNAL_LIBS TRUE) endif() ############################################################# @@ -276,7 +280,8 @@ if(WITH_CORE) endif() # try to configure and build POLY2TRI support - set (WITH_INTERNAL_POLY2TRI TRUE CACHE BOOL "Determines whether POLY2TRI should be built from internal copy") + set (WITH_INTERNAL_POLY2TRI ${PREFER_INTERNAL_LIBS} CACHE BOOL "Determines whether POLY2TRI should be built from internal copy") + set (WITH_INTERNAL_MESHOPTIMIZER ${PREFER_INTERNAL_LIBS} CACHE BOOL "Determines whether MESHOPTIMIZER should be built from internal copy") # try to configure and build POSTGRESQL support set (WITH_POSTGRESQL TRUE CACHE BOOL "Determines whether POSTGRESQL support should be built") @@ -389,7 +394,7 @@ if(WITH_CORE) find_package(EXPAT REQUIRED) find_package(Spatialindex REQUIRED) find_package(LibZip REQUIRED) - set (WITH_INTERNAL_NLOHMANN_JSON ON CACHE BOOL "Determines whether the vendored copy of nlohmann-json should be used") + set (WITH_INTERNAL_NLOHMANN_JSON ${PREFER_INTERNAL_LIBS} CACHE BOOL "Determines whether the vendored copy of nlohmann-json should be used") find_package(nlohmann_json REQUIRED) # The following bypasses the FindSQLite3 module introduced in CMake 3.14 diff --git a/INSTALL.md b/INSTALL.md index 19e33110c12..0ecbcadf000 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -26,17 +26,17 @@ Building QGIS from source - step by step * [3.11.3. Additional tools for QGIS development](#3113-additional-tools-for-qgis-development) * [4. Building on Windows](#4-building-on-windows) * [4.1. Building with Microsoft Visual Studio](#41-building-with-microsoft-visual-studio) - * [4.1.1. Visual Studio 2019 Community Edition](#411-visual-studio-2019-community-edition) + * [4.1.1. Visual Studio 2022 Community Edition](#411-visual-studio-2022-community-edition) * [4.1.2. Other tools and dependencies](#412-other-tools-and-dependencies) * [4.1.3. Clone the QGIS Source Code](#413-clone-the-qgis-source-code) - * [4.1.4. OSGeo4W](#414-OSGeo4W) + * [4.1.4. OSGeo4W](#414-osgeo4w) * [4.2. Building on Linux with mingw64](#42-building-on-linux-with-mingw64) * [4.2.1. Building with Docker](#421-building-with-docker) * [4.2.1.1. Initial setup](#4211-initial-setup) * [4.2.1.2. Building the dependencies](#4212-building-the-dependencies) * [4.2.1.3. Cross-Building QGIS](#4213-cross-building-qgis) * [4.2.2. Testing QGIS](#422-testing-qgis) - * [4.3 Building for Qt 6 with VCPKG in Microsoft Visual Studio](#41-building-with-qt6) + * [4.3 Building for Qt 6 with VCPKG in Microsoft Visual Studio](43-building-on-windows-with-vcpkg) * [5. Building on MacOS X](#5-building-on-macos-x) * [5.1. Install Developer Tools](#51-install-developer-tools) * [5.2. Install CMake and other build tools](#52-install-cmake-and-other-build-tools) diff --git a/cmake/CopyResources.cmake b/cmake/CopyResources.cmake index 8dbd8c900c5..db23dcec7ae 100644 --- a/cmake/CopyResources.cmake +++ b/cmake/CopyResources.cmake @@ -11,19 +11,28 @@ ## MACRO(ADD_QGIS_RESOURCES SOURCE_PREFIX TARGET_PREFIX DEST_FILES SOURCE_FILE_PATHS) -# On build copy all resource files to build folder +# Create a list of all copy commands, source paths and destination paths +SET(ALL_COPY_COMMANDS "") +SET(ALL_SOURCE_FILES "") FOREACH(RESOURCE_FILE ${SOURCE_FILE_PATHS}) - ADD_CUSTOM_COMMAND( - OUTPUT "${CMAKE_BINARY_DIR}/output/data/${TARGET_PREFIX}/${RESOURCE_FILE}" + LIST(APPEND ALL_COPY_COMMANDS COMMAND ${CMAKE_COMMAND} -E copy "${SOURCE_PREFIX}/${RESOURCE_FILE}" "${CMAKE_BINARY_DIR}/output/data/${TARGET_PREFIX}/${RESOURCE_FILE}" - DEPENDS "${SOURCE_PREFIX}/${RESOURCE_FILE}" ) + LIST(APPEND ALL_SOURCE_FILES "${SOURCE_PREFIX}/${RESOURCE_FILE}") LIST(APPEND ${DEST_FILES} "${CMAKE_BINARY_DIR}/output/data/${TARGET_PREFIX}/${RESOURCE_FILE}") ENDFOREACH(RESOURCE_FILE) +# Add a single custom command to install all resources to system resource folder +ADD_CUSTOM_COMMAND( + OUTPUT ${${DEST_FILES}} + ${ALL_COPY_COMMANDS} + COMMENT "Copying '${TARGET_PREFIX}' resources" + DEPENDS ${ALL_SOURCE_FILES} +) + # Install resources to system resource folder FOREACH(RESOURCE_FILE ${SOURCE_FILE_PATHS}) GET_FILENAME_COMPONENT(PATH_NAME "${TARGET_PREFIX}/${RESOURCE_FILE}" PATH) diff --git a/cmake/FindSIP.cmake b/cmake/FindSIP.cmake index 101a991d7de..b3374c7c9be 100644 --- a/cmake/FindSIP.cmake +++ b/cmake/FindSIP.cmake @@ -39,25 +39,33 @@ ELSE(SIP_VERSION) STRING(REGEX REPLACE ".*\nsip_version_num:([^\n]+).*$" "\\1" SIP_VERSION_NUM ${sip_config}) STRING(REGEX REPLACE ".*\nsip_version_str:([^\n]+).*$" "\\1" SIP_VERSION_STR ${sip_config}) STRING(REGEX REPLACE ".*\ndefault_sip_dir:([^\n]+).*$" "\\1" SIP_DEFAULT_SIP_DIR ${sip_config}) + IF(NOT SIP_FIND_QUIETLY) + MESSAGE(STATUS "Found SIP version: ${SIP_VERSION_STR}") + ENDIF(NOT SIP_FIND_QUIETLY) + IF(${SIP_VERSION_STR} VERSION_LESS 5) STRING(REGEX REPLACE ".*\nsip_bin:([^\n]+).*$" "\\1" SIP_BINARY_PATH ${sip_config}) STRING(REGEX REPLACE ".*\nsip_inc_dir:([^\n]+).*$" "\\1" SIP_INCLUDE_DIR ${sip_config}) STRING(REGEX REPLACE ".*\nsip_module_dir:([^\n]+).*$" "\\1" SIP_MODULE_DIR ${sip_config}) + SET(SIP_FOUND TRUE) ELSE(${SIP_VERSION_STR} VERSION_LESS 5) FIND_PROGRAM(SIP_BUILD_EXECUTABLE sip-build) + IF(SIP_BUILD_EXECUTABLE) + IF(NOT SIP_FIND_QUIETLY) + MESSAGE(STATUS "Found sip-build executable: ${SIP_BUILD_EXECUTABLE}") + ENDIF(NOT SIP_FIND_QUIETLY) + SET(SIP_FOUND TRUE) + ELSE(SIP_BUILD_EXECUTABLE) + MESSAGE (FATAL_ERROR "Could not find sip-build executable") + ENDIF(SIP_BUILD_EXECUTABLE) ENDIF(${SIP_VERSION_STR} VERSION_LESS 5) - SET(SIP_FOUND TRUE) ENDIF(sip_config) - IF(SIP_FOUND) - IF(NOT SIP_FIND_QUIETLY) - MESSAGE(STATUS "Found SIP version: ${SIP_VERSION_STR}") - ENDIF(NOT SIP_FIND_QUIETLY) - ELSE(SIP_FOUND) + IF(NOT SIP_FOUND) IF(SIP_FIND_REQUIRED) MESSAGE(FATAL_ERROR "Could not find SIP") ENDIF(SIP_FIND_REQUIRED) - ENDIF(SIP_FOUND) + ENDIF(NOT SIP_FOUND) ENDIF(SIP_VERSION) diff --git a/python/3d/auto_generated/qgs3dmapscene.sip.in b/python/3d/auto_generated/qgs3dmapscene.sip.in index 13c0f521db1..410b442120b 100644 --- a/python/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/3d/auto_generated/qgs3dmapscene.sip.in @@ -153,12 +153,12 @@ The number of planes is equal to the size of ``clipPlaneEquations``. A plane equation contains 4 elements. A simple way to define a clip plane equation is to define a normalized normal to the plane and its distance from the origin of the scene. -In that case, the first 3 elements are the coordinates of the normal of the plane as (X, Y, Z). +In that case, the first 3 elements are the coordinates of the normal of the plane as ``(X, Y, Z)``. They need to be normalized. The last element is the distance of the plane from the origin of the scene. -In mathematical terms, a 3d plane can be defined with the equation ax+by+cz+d=0 -The normal is (a, b, c) with |a, b, c| = 1 -The distance is -d. +In mathematical terms, a 3d plane can be defined with the equation ``ax+by+cz+d=0`` +The normal is ``(a, b, c)`` with ``|a, b, c| = 1`` +The distance is ``-d``. By default, OpenGL supports up to 8 additional clipping planes. If ``clipPlaneEquations`` contains more than 8 planes, only the first 8 ones will be used. diff --git a/python/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in b/python/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in index 0543268ad03..57c89f0d231 100644 --- a/python/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in +++ b/python/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in @@ -191,9 +191,9 @@ Returns transform for individual objects represented by the symbol Sets transform for individual objects represented by the symbol %End - QMatrix4x4 billboardTransform() const; + float billboardHeight() const; %Docstring -Returns transform for billboards +Returns how much the billboard should be elevated upwards %End private: diff --git a/python/3d/class_map.yaml b/python/3d/class_map.yaml index 27e71b4ba15..3ac968d8def 100644 --- a/python/3d/class_map.yaml +++ b/python/3d/class_map.yaml @@ -425,8 +425,8 @@ QgsPhongTexturedMaterialSettings.type: src/3d/materials/qgsphongtexturedmaterial QgsPhongTexturedMaterialSettings.writeXml: src/3d/materials/qgsphongtexturedmaterialsettings.h#L127 QgsPhongTexturedMaterialSettings: src/3d/materials/qgsphongtexturedmaterialsettings.h#L36 QgsPoint3DSymbol.altitudeClamping: src/3d/symbols/qgspoint3dsymbol.h#L64 +QgsPoint3DSymbol.billboardHeight: src/3d/symbols/qgspoint3dsymbol.h#L168 QgsPoint3DSymbol.billboardSymbol: src/3d/symbols/qgspoint3dsymbol.h#L158 -QgsPoint3DSymbol.billboardTransform: src/3d/symbols/qgspoint3dsymbol.h#L168 QgsPoint3DSymbol.clone: src/3d/symbols/qgspoint3dsymbol.h#L56 QgsPoint3DSymbol.create: src/3d/symbols/qgspoint3dsymbol.h#L53 QgsPoint3DSymbol.materialSettings: src/3d/symbols/qgspoint3dsymbol.h#L69 diff --git a/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in b/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in index 197c59bcb85..c3209dc6d09 100644 --- a/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in +++ b/python/PyQt6/3d/auto_generated/qgs3dmapscene.sip.in @@ -153,12 +153,12 @@ The number of planes is equal to the size of ``clipPlaneEquations``. A plane equation contains 4 elements. A simple way to define a clip plane equation is to define a normalized normal to the plane and its distance from the origin of the scene. -In that case, the first 3 elements are the coordinates of the normal of the plane as (X, Y, Z). +In that case, the first 3 elements are the coordinates of the normal of the plane as ``(X, Y, Z)``. They need to be normalized. The last element is the distance of the plane from the origin of the scene. -In mathematical terms, a 3d plane can be defined with the equation ax+by+cz+d=0 -The normal is (a, b, c) with |a, b, c| = 1 -The distance is -d. +In mathematical terms, a 3d plane can be defined with the equation ``ax+by+cz+d=0`` +The normal is ``(a, b, c)`` with ``|a, b, c| = 1`` +The distance is ``-d``. By default, OpenGL supports up to 8 additional clipping planes. If ``clipPlaneEquations`` contains more than 8 planes, only the first 8 ones will be used. diff --git a/python/PyQt6/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in b/python/PyQt6/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in index 0543268ad03..57c89f0d231 100644 --- a/python/PyQt6/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in +++ b/python/PyQt6/3d/auto_generated/symbols/qgspoint3dsymbol.sip.in @@ -191,9 +191,9 @@ Returns transform for individual objects represented by the symbol Sets transform for individual objects represented by the symbol %End - QMatrix4x4 billboardTransform() const; + float billboardHeight() const; %Docstring -Returns transform for billboards +Returns how much the billboard should be elevated upwards %End private: diff --git a/python/PyQt6/3d/class_map.yaml b/python/PyQt6/3d/class_map.yaml index 27e71b4ba15..3ac968d8def 100644 --- a/python/PyQt6/3d/class_map.yaml +++ b/python/PyQt6/3d/class_map.yaml @@ -425,8 +425,8 @@ QgsPhongTexturedMaterialSettings.type: src/3d/materials/qgsphongtexturedmaterial QgsPhongTexturedMaterialSettings.writeXml: src/3d/materials/qgsphongtexturedmaterialsettings.h#L127 QgsPhongTexturedMaterialSettings: src/3d/materials/qgsphongtexturedmaterialsettings.h#L36 QgsPoint3DSymbol.altitudeClamping: src/3d/symbols/qgspoint3dsymbol.h#L64 +QgsPoint3DSymbol.billboardHeight: src/3d/symbols/qgspoint3dsymbol.h#L168 QgsPoint3DSymbol.billboardSymbol: src/3d/symbols/qgspoint3dsymbol.h#L158 -QgsPoint3DSymbol.billboardTransform: src/3d/symbols/qgspoint3dsymbol.h#L168 QgsPoint3DSymbol.clone: src/3d/symbols/qgspoint3dsymbol.h#L56 QgsPoint3DSymbol.create: src/3d/symbols/qgspoint3dsymbol.h#L53 QgsPoint3DSymbol.materialSettings: src/3d/symbols/qgspoint3dsymbol.h#L69 diff --git a/python/PyQt6/core/__init__.py.in b/python/PyQt6/core/__init__.py.in index 2ee442b06f9..aa7b79a1323 100644 --- a/python/PyQt6/core/__init__.py.in +++ b/python/PyQt6/core/__init__.py.in @@ -653,9 +653,6 @@ except ModuleNotFoundError: QgsGeometry.as_shapely = _geometry_as_shapely - - - QgsRasterBlock.as_numpy.__doc__ = """ Returns the block data as a numpy array. @@ -665,6 +662,7 @@ If `use_masking` is `True` then the returned array will be a numpy masked array, .. versionadded:: 3.40 """ + QgsRasterLayer.as_numpy.__doc__ = """ Returns the layer data as a numpy array. @@ -676,6 +674,19 @@ If `bands` is provided, only the specified bands will be included in the returne .. versionadded:: 3.40 """ -QgsGeometry.as_numpy.__doc__ = """Returns the geometry data as a numpy array or list of numpy arrays.""" -QgsGeometry.as_shapely.__doc__ = """Returns the geometry data as a shapely object.""" +QgsGeometry.as_numpy.__doc__ = """ +Returns the geometry data as a numpy array or list of numpy arrays. + +:raises QgsNotSupportedException: if numpy is not available on the system + +.. versionadded:: 3.40 +""" + +QgsGeometry.as_shapely.__doc__ = """ +Returns the geometry data as a shapely object. + +:raises QgsNotSupportedException: if shapely is not available on the system + +.. versionadded:: 3.40 +""" diff --git a/python/PyQt6/core/auto_additions/qgslayoutexporter.py b/python/PyQt6/core/auto_additions/qgslayoutexporter.py index 68b79e5c9c8..85282860724 100644 --- a/python/PyQt6/core/auto_additions/qgslayoutexporter.py +++ b/python/PyQt6/core/auto_additions/qgslayoutexporter.py @@ -13,7 +13,7 @@ try: except NameError: pass try: - QgsLayoutExporter.ImageExportSettings.__attribute_docs__ = {'dpi': 'Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.', 'imageSize': "Manual size in pixels for output image. If imageSize is not\nset then it will be automatically calculated based on the\noutput dpi and layout size.\n\nIf cropToContents is ``True`` then imageSize has no effect.\n\nBe careful when specifying manual sizes if pages in the layout\nhave differing sizes! It's likely not going to give a reasonable\noutput in this case, and the automatic dpi-based image size should be\nused instead.", 'cropToContents': 'Set to ``True`` if image should be cropped so only parts of the layout\ncontaining items are exported.', 'cropMargins': 'Crop to content margins, in pixels. These margins will be added\nto the bounds of the exported layout if cropToContents is ``True``.', 'pages': 'List of specific pages to export, or an empty list to\nexport all pages.\n\nPage numbers are 0 index based, so the first page in the\nlayout corresponds to page 0.', 'generateWorldFile': 'Set to ``True`` to generate an external world file alongside\nexported images.', 'exportMetadata': "Indicates whether image export should include metadata generated\nfrom the layout's project's metadata.\n\n.. versionadded:: 3.2", 'flags': 'Layout context flags, which control how the export will be created.', 'predefinedMapScales': 'A list of predefined scales to use with the layout. This is used\nfor maps which are set to the predefined atlas scaling mode.\n\n.. versionadded:: 3.10'} + QgsLayoutExporter.ImageExportSettings.__attribute_docs__ = {'dpi': 'Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.', 'imageSize': "Manual size in pixels for output image. If imageSize is not\nset then it will be automatically calculated based on the\noutput dpi and layout size.\n\nIf cropToContents is ``True`` then imageSize has no effect.\n\nBe careful when specifying manual sizes if pages in the layout\nhave differing sizes! It's likely not going to give a reasonable\noutput in this case, and the automatic dpi-based image size should be\nused instead.", 'cropToContents': 'Set to ``True`` if image should be cropped so only parts of the layout\ncontaining items are exported.', 'cropMargins': 'Crop to content margins, in pixels. These margins will be added\nto the bounds of the exported layout if cropToContents is ``True``.', 'pages': 'List of specific pages to export, or an empty list to\nexport all pages.\n\nPage numbers are 0 index based, so the first page in the\nlayout corresponds to page 0.', 'generateWorldFile': 'Set to ``True`` to generate an external world file alongside\nexported images.', 'exportMetadata': "Indicates whether image export should include metadata generated\nfrom the layout's project's metadata.\n\n.. versionadded:: 3.2", 'flags': 'Layout context flags, which control how the export will be created.', 'predefinedMapScales': 'A list of predefined scales to use with the layout. This is used\nfor maps which are set to the predefined atlas scaling mode.\n\n.. versionadded:: 3.10', 'quality': 'Image quality, typically used for JPEG compression (whose quality ranges from 1 to 100)\nif quality is set to -1, the default quality will be used.\n\n.. versionadded:: 3.42'} QgsLayoutExporter.ImageExportSettings.__doc__ = """Contains settings relating to exporting layouts to raster images""" QgsLayoutExporter.ImageExportSettings.__group__ = ['layout'] except NameError: diff --git a/python/PyQt6/core/auto_additions/qgsrenderchecker.py b/python/PyQt6/core/auto_additions/qgsrenderchecker.py index f797e96ce7d..9a43668bc84 100644 --- a/python/PyQt6/core/auto_additions/qgsrenderchecker.py +++ b/python/PyQt6/core/auto_additions/qgsrenderchecker.py @@ -1,11 +1,16 @@ # The following has been generated automatically from src/core/qgsrenderchecker.h # monkey patching scoped based enum QgsRenderChecker.Flag.AvoidExportingRenderedImage.__doc__ = "Avoids exporting rendered images to reports" +QgsRenderChecker.Flag.Silent.__doc__ = "Don't output non-critical messages to console \n.. versionadded:: 3.40" QgsRenderChecker.Flag.__doc__ = """Render checker flags. .. versionadded:: 3.28 * ``AvoidExportingRenderedImage``: Avoids exporting rendered images to reports +* ``Silent``: Don't output non-critical messages to console + + .. versionadded:: 3.40 + """ # -- diff --git a/python/PyQt6/core/auto_additions/qgstemporalutils.py b/python/PyQt6/core/auto_additions/qgstemporalutils.py index 6c0172389aa..19183e9a845 100644 --- a/python/PyQt6/core/auto_additions/qgstemporalutils.py +++ b/python/PyQt6/core/auto_additions/qgstemporalutils.py @@ -1,6 +1,6 @@ # The following has been generated automatically from src/core/qgstemporalutils.h try: - QgsTemporalUtils.AnimationExportSettings.__attribute_docs__ = {'animationRange': 'Dictates the overall temporal range of the animation.', 'frameDuration': 'Duration of individual export frames', 'outputDirectory': 'Destination directory for created image files.', 'fileNameTemplate': "The filename template for exporting the frames.\n\nThis must be in format prefix####.format, where number of\n``# ``characters represents how many 0's should be left-padded to the frame number\ne.g. my###.jpg will create frames my001.jpg, my002.jpg, etc", 'decorations': 'List of decorations to draw onto exported frames.', 'availableTemporalRanges': 'Contains the list of all available temporal ranges which have data available.\n\nThe list can be a list of non-contiguous ranges (i.e. containing gaps)\nwhich together describe the complete range of times which contain data.\n\nThis list is required whenever the :py:class:`QgsUnitTypes`.TemporalIrregularStep interval is used\nfor an animation.\n\n.. versionadded:: 3.30', 'frameRate': 'Target animation frame rate in frames per second.\n\n.. versionadded:: 3.26'} + QgsTemporalUtils.AnimationExportSettings.__attribute_docs__ = {'animationRange': 'Dictates the overall temporal range of the animation.', 'frameDuration': 'Duration of individual export frames', 'outputDirectory': 'Destination directory for created image files.', 'fileNameTemplate': "The filename template for exporting the frames.\n\nThis must be in format ``prefix####.format``, where number of\n````#`` ``characters represents how many 0's should be left-padded to the frame number\ne.g. ``my###.jpg`` will create frames ``my001.jpg``, ``my002.jpg``, etc", 'decorations': 'List of decorations to draw onto exported frames.', 'availableTemporalRanges': 'Contains the list of all available temporal ranges which have data available.\n\nThe list can be a list of non-contiguous ranges (i.e. containing gaps)\nwhich together describe the complete range of times which contain data.\n\nThis list is required whenever the :py:class:`QgsUnitTypes`.TemporalIrregularStep interval is used\nfor an animation.\n\n.. versionadded:: 3.30', 'frameRate': 'Target animation frame rate in frames per second.\n\n.. versionadded:: 3.26'} QgsTemporalUtils.AnimationExportSettings.__doc__ = """Contains settings relating to exporting animations""" except NameError: pass diff --git a/python/PyQt6/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in b/python/PyQt6/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in index 448bf01eb52..662236f27b8 100644 --- a/python/PyQt6/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in +++ b/python/PyQt6/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in @@ -80,7 +80,7 @@ Initializes the storage. .. note:: - The default implementation does nothing and returns ``True``. + The default implementation does nothing and returns ``True``. This method is called by the authentication manager when the storage is added to the manager. %End diff --git a/python/PyQt6/core/auto_generated/geometry/qgsbox3d.sip.in b/python/PyQt6/core/auto_generated/geometry/qgsbox3d.sip.in index 8985b69322a..c3205eeb8b4 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgsbox3d.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgsbox3d.sip.in @@ -36,7 +36,7 @@ extent of a geometry or collection of geometries. public: - QgsBox3D( SIP_PYOBJECT x /TypeHint="Optional[Union[QgsPoint, QgsRectangle, float]]"/ = Py_None, SIP_PYOBJECT y /TypeHint="Optional[QgsPoint, float]"/ = Py_None, SIP_PYOBJECT z /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT x2 /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT y2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT z2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT n /TypeHint="Optional[bool]"/ = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double x2 = 0.0, double y2 = 0.0, double z2 = 0.0, bool n = true )]; + QgsBox3D( SIP_PYOBJECT x /TypeHint="Optional[Union[QgsPoint, QgsVector3D, QgsRectangle, float]]"/ = Py_None, SIP_PYOBJECT y /TypeHint="Optional[QgsPoint, QgsVector3D, float]"/ = Py_None, SIP_PYOBJECT z /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT x2 /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT y2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT z2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT n /TypeHint="Optional[bool]"/ = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double x2 = 0.0, double y2 = 0.0, double z2 = 0.0, bool n = true )]; %Docstring Constructor for QgsBox3D which accepts the ranges of x/y/z coordinates. If ``normalize`` is ``False`` then the normalization step will not be applied automatically. @@ -85,6 +85,30 @@ the normalization step will not be applied automatically. } } } + else if ( sipCanConvertToType( a0, sipType_QgsVector3D, SIP_NOT_NONE ) && sipCanConvertToType( a1, sipType_QgsVector3D, SIP_NOT_NONE ) && a3 == Py_None && a4 == Py_None && a5 == Py_None && a6 == Py_None ) + { + int state; + sipIsErr = 0; + + QgsVector3D *corner1 = reinterpret_cast( sipConvertToType( a0, sipType_QgsVector3D, 0, SIP_NOT_NONE, &state, &sipIsErr ) ); + if ( sipIsErr ) + { + sipReleaseType( corner1, sipType_QgsVector3D, state ); + } + else + { + QgsVector3D *corner2 = reinterpret_cast( sipConvertToType( a1, sipType_QgsVector3D, 0, SIP_NOT_NONE, &state, &sipIsErr ) ); + if ( sipIsErr ) + { + sipReleaseType( corner2, sipType_QgsVector3D, state ); + } + else + { + bool n = a2 == Py_None ? true : PyObject_IsTrue( a2 ); + sipCpp = new QgsBox3D( *corner1, *corner2, n ); + } + } + } else if ( ( a0 == Py_None || PyFloat_AsDouble( a0 ) != -1.0 || !PyErr_Occurred() ) && ( a1 == Py_None || PyFloat_AsDouble( a1 ) != -1.0 || !PyErr_Occurred() ) && @@ -375,6 +399,13 @@ If no ``center`` point is specified then the current center of the box will be u Scale the rectangle around a center coordinates. .. versionadded:: 3.26 +%End + + void grow( double delta ); +%Docstring +Grows the box in place by the specified amount in all dimensions. + +.. versionadded:: 3.42 %End bool isNull() const /HoldGIL/; diff --git a/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in b/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in index 449e6c591bb..b43a47c1502 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2957,7 +2957,7 @@ geometry will retain the same dimensionality as the input geometry. :param maxAngle: maximum angle at node (0-180) at which smoothing will be applied %End - static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0 ) /Factory/; + static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) /Factory/; %Docstring Creates and returns a new geometry engine representing the specified ``geometry`` using ``precision`` on a grid. The ``precision`` argument was added in 3.36. @@ -2972,6 +2972,8 @@ Many methods available in the :py:class:`QgsGeometryEngine` class can benefit fr a large number of spatial relationships will be tested (such as calling :py:func:`~QgsGeometry.intersects`, :py:func:`~QgsGeometry.within`, etc) then the geometry should first be prepared by calling :py:func:`~QgsGeometry.prepareGeometry` before performing the tests. +The ``flags`` argument was added in QGIS 3.40 to allow control over the resultant GEOS geometry. + Example ------------------------------------- diff --git a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in index 1b9a340f1b6..539b29a2112 100644 --- a/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/PyQt6/core/auto_generated/geometry/qgslinestring.sip.in @@ -643,6 +643,17 @@ If ``useZValues`` is ``True`` then z values will also be considered when testing + QVector splitToDisjointXYParts() const /Factory/; +%Docstring +Divides the linestring into parts that don't share any points or lines. + +This method throws away Z and M coordinates. + +The ownership of returned pointers is transferred to the caller. + +.. versionadded:: 3.40 +%End + double length3D() const /HoldGIL/; %Docstring Returns the length in 3D world of the line string. diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutexporter.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutexporter.sip.in index 754e836273c..62ec0cdff30 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutexporter.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutexporter.sip.in @@ -24,6 +24,7 @@ Handles rendering and exports of layouts to various formats. + struct PageExportDetails { QString directory; @@ -142,6 +143,9 @@ Returns the rendered image, or a null QImage if the image does not fit into avai QVector predefinedMapScales; + + int quality; + }; ExportResult exportToImage( const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings ); diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in index 93a5be09e5c..7d9e0df6dce 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in @@ -111,6 +111,15 @@ Deselects any selected nodes. virtual double estimatedFrameBleed() const; + virtual bool isValid() const = 0; +%Docstring +Must be reimplemented in subclasses. +Typically a polyline is valid if it has at least 2 distinct nodes, +while a polygon is valid if it has at least 3 distinct nodes. + +.. versionadded:: 3.40 +%End + protected: QgsLayoutNodesItem( QgsLayout *layout ); diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutitempolygon.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutitempolygon.sip.in index e1486ac9f7b..e41497482b9 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutitempolygon.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutitempolygon.sip.in @@ -53,6 +53,8 @@ The caller takes responsibility for deleting the returned object. virtual QgsGeometry clipPath() const; + virtual bool isValid() const; + QgsFillSymbol *symbol(); %Docstring diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutitempolyline.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutitempolyline.sip.in index 979af0467d4..593ab28fbc1 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutitempolyline.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutitempolyline.sip.in @@ -55,6 +55,8 @@ The caller takes responsibility for deleting the returned object. virtual QPainterPath shape() const; + virtual bool isValid() const; + QgsLineSymbol *symbol(); %Docstring diff --git a/python/PyQt6/core/auto_generated/layout/qgslayoutpagecollection.sip.in b/python/PyQt6/core/auto_generated/layout/qgslayoutpagecollection.sip.in index 0c2509bbb82..e025acb815c 100644 --- a/python/PyQt6/core/auto_generated/layout/qgslayoutpagecollection.sip.in +++ b/python/PyQt6/core/auto_generated/layout/qgslayoutpagecollection.sip.in @@ -368,6 +368,13 @@ Returns a reference to the collection's guide collection, which manages page sna %End + void applyPropertiesToAllOtherPages( int sourcePage ); +%Docstring +Apply the source page properties (size & background color) to all other pages + +.. versionadded:: 3.42 +%End + public slots: void redraw(); diff --git a/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in b/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in index 3de25b9e682..fa0bf644f59 100644 --- a/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in +++ b/python/PyQt6/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in @@ -301,6 +301,7 @@ Emitted when point cloud generation state is changed protected: + }; /************************************************************************ diff --git a/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in b/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in index e854c08109f..6306aa0c441 100644 --- a/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in +++ b/python/PyQt6/core/auto_generated/qgsrenderchecker.sip.in @@ -183,6 +183,7 @@ Sets the largest allowable difference in size between the rendered and the expec enum class Flag /BaseType=IntFlag/ { AvoidExportingRenderedImage, + Silent, }; typedef QFlags Flags; diff --git a/python/PyQt6/core/class_map.yaml b/python/PyQt6/core/class_map.yaml index 5fc369f2c91..4804ccce313 100644 --- a/python/PyQt6/core/class_map.yaml +++ b/python/PyQt6/core/class_map.yaml @@ -1345,66 +1345,66 @@ QgsAuthConfigSslServer.sslPeerVerifyMode: src/core/auth/qgsauthconfig.h#L418 QgsAuthConfigSslServer.sslProtocol: src/core/auth/qgsauthconfig.h#L406 QgsAuthConfigSslServer.version: src/core/auth/qgsauthconfig.h#L435 QgsAuthConfigSslServer: src/core/auth/qgsauthconfig.h#L389 -QgsAuthConfigurationStorage.authMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L164 -QgsAuthConfigurationStorage.authMethodConfigsWithPayload: src/core/auth/qgsauthconfigurationstorage.h#L174 -QgsAuthConfigurationStorage.authSettingExists: src/core/auth/qgsauthconfigurationstorage.h#L241 -QgsAuthConfigurationStorage.authSettingsChanged: src/core/auth/qgsauthconfigurationstorage.h#L524 -QgsAuthConfigurationStorage.capabilities: src/core/auth/qgsauthconfigurationstorage.h#L118 -QgsAuthConfigurationStorage.certAuthorityChanged: src/core/auth/qgsauthconfigurationstorage.h#L542 -QgsAuthConfigurationStorage.certAuthorityExists: src/core/auth/qgsauthconfigurationstorage.h#L394 -QgsAuthConfigurationStorage.certAuthorityIds: src/core/auth/qgsauthconfigurationstorage.h#L378 -QgsAuthConfigurationStorage.certIdentityChanged: src/core/auth/qgsauthconfigurationstorage.h#L537 -QgsAuthConfigurationStorage.certIdentityExists: src/core/auth/qgsauthconfigurationstorage.h#L298 -QgsAuthConfigurationStorage.certIdentityIds: src/core/auth/qgsauthconfigurationstorage.h#L290 -QgsAuthConfigurationStorage.certTrustPolicyExists: src/core/auth/qgsauthconfigurationstorage.h#L449 -QgsAuthConfigurationStorage.clearMasterPasswords: src/core/auth/qgsauthconfigurationstorage.h#L473 -QgsAuthConfigurationStorage.clearMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L488 +QgsAuthConfigurationStorage.authMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L165 +QgsAuthConfigurationStorage.authMethodConfigsWithPayload: src/core/auth/qgsauthconfigurationstorage.h#L175 +QgsAuthConfigurationStorage.authSettingExists: src/core/auth/qgsauthconfigurationstorage.h#L242 +QgsAuthConfigurationStorage.authSettingsChanged: src/core/auth/qgsauthconfigurationstorage.h#L525 +QgsAuthConfigurationStorage.capabilities: src/core/auth/qgsauthconfigurationstorage.h#L119 +QgsAuthConfigurationStorage.certAuthorityChanged: src/core/auth/qgsauthconfigurationstorage.h#L543 +QgsAuthConfigurationStorage.certAuthorityExists: src/core/auth/qgsauthconfigurationstorage.h#L395 +QgsAuthConfigurationStorage.certAuthorityIds: src/core/auth/qgsauthconfigurationstorage.h#L379 +QgsAuthConfigurationStorage.certIdentityChanged: src/core/auth/qgsauthconfigurationstorage.h#L538 +QgsAuthConfigurationStorage.certIdentityExists: src/core/auth/qgsauthconfigurationstorage.h#L299 +QgsAuthConfigurationStorage.certIdentityIds: src/core/auth/qgsauthconfigurationstorage.h#L291 +QgsAuthConfigurationStorage.certTrustPolicyExists: src/core/auth/qgsauthconfigurationstorage.h#L450 +QgsAuthConfigurationStorage.clearMasterPasswords: src/core/auth/qgsauthconfigurationstorage.h#L474 +QgsAuthConfigurationStorage.clearMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L489 QgsAuthConfigurationStorage.description: src/core/auth/qgsauthconfigurationstorage.h#L87 -QgsAuthConfigurationStorage.erase: src/core/auth/qgsauthconfigurationstorage.h#L480 +QgsAuthConfigurationStorage.erase: src/core/auth/qgsauthconfigurationstorage.h#L481 QgsAuthConfigurationStorage.id: src/core/auth/qgsauthconfigurationstorage.h#L93 -QgsAuthConfigurationStorage.initialize: src/core/auth/qgsauthconfigurationstorage.h#L102 -QgsAuthConfigurationStorage.isEnabled: src/core/auth/qgsauthconfigurationstorage.h#L138 -QgsAuthConfigurationStorage.isEncrypted: src/core/auth/qgsauthconfigurationstorage.h#L133 -QgsAuthConfigurationStorage.isReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L157 -QgsAuthConfigurationStorage.isReady: src/core/auth/qgsauthconfigurationstorage.h#L113 -QgsAuthConfigurationStorage.lastError: src/core/auth/qgsauthconfigurationstorage.h#L107 -QgsAuthConfigurationStorage.loadAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L225 -QgsAuthConfigurationStorage.loadCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L386 -QgsAuthConfigurationStorage.loadCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L268 -QgsAuthConfigurationStorage.loadCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L433 -QgsAuthConfigurationStorage.loadMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L184 -QgsAuthConfigurationStorage.loadSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L323 -QgsAuthConfigurationStorage.loadSslCertCustomConfigByHost: src/core/auth/qgsauthconfigurationstorage.h#L331 -QgsAuthConfigurationStorage.loggerTag: src/core/auth/qgsauthconfigurationstorage.h#L578 -QgsAuthConfigurationStorage.masterPasswordChanged: src/core/auth/qgsauthconfigurationstorage.h#L519 -QgsAuthConfigurationStorage.messageLog: src/core/auth/qgsauthconfigurationstorage.h#L500 -QgsAuthConfigurationStorage.methodConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L514 -QgsAuthConfigurationStorage.methodConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L208 +QgsAuthConfigurationStorage.initialize: src/core/auth/qgsauthconfigurationstorage.h#L103 +QgsAuthConfigurationStorage.isEnabled: src/core/auth/qgsauthconfigurationstorage.h#L139 +QgsAuthConfigurationStorage.isEncrypted: src/core/auth/qgsauthconfigurationstorage.h#L134 +QgsAuthConfigurationStorage.isReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L158 +QgsAuthConfigurationStorage.isReady: src/core/auth/qgsauthconfigurationstorage.h#L114 +QgsAuthConfigurationStorage.lastError: src/core/auth/qgsauthconfigurationstorage.h#L108 +QgsAuthConfigurationStorage.loadAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L226 +QgsAuthConfigurationStorage.loadCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L387 +QgsAuthConfigurationStorage.loadCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L269 +QgsAuthConfigurationStorage.loadCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L434 +QgsAuthConfigurationStorage.loadMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L185 +QgsAuthConfigurationStorage.loadSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L324 +QgsAuthConfigurationStorage.loadSslCertCustomConfigByHost: src/core/auth/qgsauthconfigurationstorage.h#L332 +QgsAuthConfigurationStorage.loggerTag: src/core/auth/qgsauthconfigurationstorage.h#L579 +QgsAuthConfigurationStorage.masterPasswordChanged: src/core/auth/qgsauthconfigurationstorage.h#L520 +QgsAuthConfigurationStorage.messageLog: src/core/auth/qgsauthconfigurationstorage.h#L501 +QgsAuthConfigurationStorage.methodConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L515 +QgsAuthConfigurationStorage.methodConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L209 QgsAuthConfigurationStorage.name: src/core/auth/qgsauthconfigurationstorage.h#L74 -QgsAuthConfigurationStorage.readOnlyChanged: src/core/auth/qgsauthconfigurationstorage.h#L529 -QgsAuthConfigurationStorage.removeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L233 -QgsAuthConfigurationStorage.removeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L402 -QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L260 -QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L306 -QgsAuthConfigurationStorage.removeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L441 -QgsAuthConfigurationStorage.removeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L200 -QgsAuthConfigurationStorage.removeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L363 -QgsAuthConfigurationStorage.setCapabilities: src/core/auth/qgsauthconfigurationstorage.h#L561 -QgsAuthConfigurationStorage.setEnabled: src/core/auth/qgsauthconfigurationstorage.h#L144 -QgsAuthConfigurationStorage.setError: src/core/auth/qgsauthconfigurationstorage.h#L566 -QgsAuthConfigurationStorage.setReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L151 -QgsAuthConfigurationStorage.sslCertCustomConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L547 -QgsAuthConfigurationStorage.sslCertCustomConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L354 -QgsAuthConfigurationStorage.sslCertCustomConfigIds: src/core/auth/qgsauthconfigurationstorage.h#L345 -QgsAuthConfigurationStorage.sslCertTrustPolicyChanged: src/core/auth/qgsauthconfigurationstorage.h#L552 -QgsAuthConfigurationStorage.storageChanged: src/core/auth/qgsauthconfigurationstorage.h#L509 -QgsAuthConfigurationStorage.storeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L217 -QgsAuthConfigurationStorage.storeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L371 -QgsAuthConfigurationStorage.storeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L252 -QgsAuthConfigurationStorage.storeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L425 -QgsAuthConfigurationStorage.storeMasterPassword: src/core/auth/qgsauthconfigurationstorage.h#L466 -QgsAuthConfigurationStorage.storeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L193 -QgsAuthConfigurationStorage.storeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L314 +QgsAuthConfigurationStorage.readOnlyChanged: src/core/auth/qgsauthconfigurationstorage.h#L530 +QgsAuthConfigurationStorage.removeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L234 +QgsAuthConfigurationStorage.removeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L403 +QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L261 +QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L307 +QgsAuthConfigurationStorage.removeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L442 +QgsAuthConfigurationStorage.removeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L201 +QgsAuthConfigurationStorage.removeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L364 +QgsAuthConfigurationStorage.setCapabilities: src/core/auth/qgsauthconfigurationstorage.h#L562 +QgsAuthConfigurationStorage.setEnabled: src/core/auth/qgsauthconfigurationstorage.h#L145 +QgsAuthConfigurationStorage.setError: src/core/auth/qgsauthconfigurationstorage.h#L567 +QgsAuthConfigurationStorage.setReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L152 +QgsAuthConfigurationStorage.sslCertCustomConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L548 +QgsAuthConfigurationStorage.sslCertCustomConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L355 +QgsAuthConfigurationStorage.sslCertCustomConfigIds: src/core/auth/qgsauthconfigurationstorage.h#L346 +QgsAuthConfigurationStorage.sslCertTrustPolicyChanged: src/core/auth/qgsauthconfigurationstorage.h#L553 +QgsAuthConfigurationStorage.storageChanged: src/core/auth/qgsauthconfigurationstorage.h#L510 +QgsAuthConfigurationStorage.storeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L218 +QgsAuthConfigurationStorage.storeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L372 +QgsAuthConfigurationStorage.storeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L253 +QgsAuthConfigurationStorage.storeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L426 +QgsAuthConfigurationStorage.storeMasterPassword: src/core/auth/qgsauthconfigurationstorage.h#L467 +QgsAuthConfigurationStorage.storeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L194 +QgsAuthConfigurationStorage.storeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L315 QgsAuthConfigurationStorage.type: src/core/auth/qgsauthconfigurationstorage.h#L81 QgsAuthConfigurationStorage: src/core/auth/qgsauthconfigurationstorage.h#L36 QgsAuthConfigurationStorageDb.authDatabaseConnection: src/core/auth/qgsauthconfigurationstoragedb.h#L75 @@ -4884,7 +4884,7 @@ QgsGeometry.Error.hasWhere: src/core/geometry/qgsgeometry.h#L2776 QgsGeometry.Error.what: src/core/geometry/qgsgeometry.h#L2766 QgsGeometry.Error.where: src/core/geometry/qgsgeometry.h#L2771 QgsGeometry.Error: src/core/geometry/qgsgeometry.h#L2747 -QgsGeometry.QVariant: src/core/geometry/qgsgeometry.h#L3183 +QgsGeometry.QVariant: src/core/geometry/qgsgeometry.h#L3185 QgsGeometry.QgsGeometry: src/core/geometry/qgsgeometry.h#L184 QgsGeometry.__repr__: src/core/geometry/qgsgeometry.h#L2185 QgsGeometry.addPart: src/core/geometry/qgsgeometry.h#L930 @@ -4938,8 +4938,8 @@ QgsGeometry.contains: src/core/geometry/qgsgeometry.h#L1465 QgsGeometry.contains: src/core/geometry/qgsgeometry.h#L1472 QgsGeometry.contains: src/core/geometry/qgsgeometry.h#L1483 QgsGeometry.convertGeometryCollectionToSubclass: src/core/geometry/qgsgeometry.h#L2609 -QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3170 -QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3177 +QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3172 +QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3179 QgsGeometry.convertToCurvedMultiType: src/core/geometry/qgsgeometry.h#L2587 QgsGeometry.convertToCurves: src/core/geometry/qgsgeometry.h#L1674 QgsGeometry.convertToMultiType: src/core/geometry/qgsgeometry.h#L2570 @@ -4947,7 +4947,7 @@ QgsGeometry.convertToSingleType: src/core/geometry/qgsgeometry.h#L2598 QgsGeometry.convertToStraightSegment: src/core/geometry/qgsgeometry.h#L2843 QgsGeometry.convertToType: src/core/geometry/qgsgeometry.h#L2252 QgsGeometry.convexHull: src/core/geometry/qgsgeometry.h#L1804 -QgsGeometry.createGeometryEngine: src/core/geometry/qgsgeometry.h#L3163 +QgsGeometry.createGeometryEngine: src/core/geometry/qgsgeometry.h#L3165 QgsGeometry.createPolygonFromQPolygonF: src/core/geometry/qgsgeometry.h#L2952 QgsGeometry.createPolylineFromQPolygonF: src/core/geometry/qgsgeometry.h#L2943 QgsGeometry.createWedgeBuffer: src/core/geometry/qgsgeometry.h#L347 @@ -6710,29 +6710,29 @@ QgsLayoutEffect.compositionMode: src/core/layout/qgslayouteffect.h#L60 QgsLayoutEffect.draw: src/core/layout/qgslayouteffect.h#L64 QgsLayoutEffect.setCompositionMode: src/core/layout/qgslayouteffect.h#L51 QgsLayoutEffect: src/core/layout/qgslayouteffect.h#L36 -QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L664 -QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L671 -QgsLayoutExporter.containsAdvancedEffects: src/core/layout/qgslayoutexporter.h#L690 -QgsLayoutExporter.errorFile: src/core/layout/qgslayoutexporter.h#L608 -QgsLayoutExporter.errorMessage: src/core/layout/qgslayoutexporter.h#L615 -QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L246 -QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L259 -QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L406 -QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L419 -QgsLayoutExporter.exportToPdfs: src/core/layout/qgslayoutexporter.h#L435 -QgsLayoutExporter.exportToSvg: src/core/layout/qgslayoutexporter.h#L587 +QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L676 +QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L683 +QgsLayoutExporter.containsAdvancedEffects: src/core/layout/qgslayoutexporter.h#L702 +QgsLayoutExporter.errorFile: src/core/layout/qgslayoutexporter.h#L620 +QgsLayoutExporter.errorMessage: src/core/layout/qgslayoutexporter.h#L627 +QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L258 +QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L271 +QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L418 +QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L431 +QgsLayoutExporter.exportToPdfs: src/core/layout/qgslayoutexporter.h#L447 QgsLayoutExporter.exportToSvg: src/core/layout/qgslayoutexporter.h#L599 -QgsLayoutExporter.generateFileName: src/core/layout/qgslayoutexporter.h#L699 -QgsLayoutExporter.georeferenceOutput: src/core/layout/qgslayoutexporter.h#L655 -QgsLayoutExporter.layout: src/core/layout/qgslayoutexporter.h#L90 -QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L480 -QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L489 -QgsLayoutExporter.renderPage: src/core/layout/qgslayoutexporter.h#L100 -QgsLayoutExporter.renderPageToImage: src/core/layout/qgslayoutexporter.h#L120 -QgsLayoutExporter.renderRegion: src/core/layout/qgslayoutexporter.h#L129 -QgsLayoutExporter.renderRegionToImage: src/core/layout/qgslayoutexporter.h#L147 -QgsLayoutExporter.requiresRasterization: src/core/layout/qgslayoutexporter.h#L681 -QgsLayoutExporter: src/core/layout/qgslayoutexporter.h#L50 +QgsLayoutExporter.exportToSvg: src/core/layout/qgslayoutexporter.h#L611 +QgsLayoutExporter.generateFileName: src/core/layout/qgslayoutexporter.h#L711 +QgsLayoutExporter.georeferenceOutput: src/core/layout/qgslayoutexporter.h#L667 +QgsLayoutExporter.layout: src/core/layout/qgslayoutexporter.h#L94 +QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L492 +QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L501 +QgsLayoutExporter.renderPage: src/core/layout/qgslayoutexporter.h#L104 +QgsLayoutExporter.renderPageToImage: src/core/layout/qgslayoutexporter.h#L124 +QgsLayoutExporter.renderRegion: src/core/layout/qgslayoutexporter.h#L133 +QgsLayoutExporter.renderRegionToImage: src/core/layout/qgslayoutexporter.h#L151 +QgsLayoutExporter.requiresRasterization: src/core/layout/qgslayoutexporter.h#L693 +QgsLayoutExporter: src/core/layout/qgslayoutexporter.h#L51 QgsLayoutFrame.cleanup: src/core/layout/qgslayoutframe.h#L54 QgsLayoutFrame.create: src/core/layout/qgslayoutframe.h#L46 QgsLayoutFrame.createExpressionContext: src/core/layout/qgslayoutframe.h#L112 @@ -7567,53 +7567,55 @@ QgsLayoutItemPicture.svgStrokeWidth: src/core/layout/qgslayoutitempicture.h#L209 QgsLayoutItemPicture.type: src/core/layout/qgslayoutitempicture.h#L63 QgsLayoutItemPicture.writePropertiesToElement: src/core/layout/qgslayoutitempicture.h#L313 QgsLayoutItemPicture: src/core/layout/qgslayoutitempicture.h#L34 -QgsLayoutItemPolygon._addNode: src/core/layout/qgslayoutitempolygon.h#L76 -QgsLayoutItemPolygon._draw: src/core/layout/qgslayoutitempolygon.h#L78 -QgsLayoutItemPolygon._readXmlStyle: src/core/layout/qgslayoutitempolygon.h#L79 -QgsLayoutItemPolygon._removeNode: src/core/layout/qgslayoutitempolygon.h#L77 -QgsLayoutItemPolygon._writeXmlStyle: src/core/layout/qgslayoutitempolygon.h#L80 +QgsLayoutItemPolygon._addNode: src/core/layout/qgslayoutitempolygon.h#L77 +QgsLayoutItemPolygon._draw: src/core/layout/qgslayoutitempolygon.h#L79 +QgsLayoutItemPolygon._readXmlStyle: src/core/layout/qgslayoutitempolygon.h#L80 +QgsLayoutItemPolygon._removeNode: src/core/layout/qgslayoutitempolygon.h#L78 +QgsLayoutItemPolygon._writeXmlStyle: src/core/layout/qgslayoutitempolygon.h#L81 QgsLayoutItemPolygon.accept: src/core/layout/qgslayoutitempolygon.h#L58 QgsLayoutItemPolygon.clipPath: src/core/layout/qgslayoutitempolygon.h#L60 QgsLayoutItemPolygon.create: src/core/layout/qgslayoutitempolygon.h#L53 QgsLayoutItemPolygon.displayName: src/core/layout/qgslayoutitempolygon.h#L57 QgsLayoutItemPolygon.icon: src/core/layout/qgslayoutitempolygon.h#L56 +QgsLayoutItemPolygon.isValid: src/core/layout/qgslayoutitempolygon.h#L61 QgsLayoutItemPolygon.itemFlags: src/core/layout/qgslayoutitempolygon.h#L59 -QgsLayoutItemPolygon.setSymbol: src/core/layout/qgslayoutitempolygon.h#L73 -QgsLayoutItemPolygon.symbol: src/core/layout/qgslayoutitempolygon.h#L66 +QgsLayoutItemPolygon.setSymbol: src/core/layout/qgslayoutitempolygon.h#L74 +QgsLayoutItemPolygon.symbol: src/core/layout/qgslayoutitempolygon.h#L67 QgsLayoutItemPolygon.type: src/core/layout/qgslayoutitempolygon.h#L55 QgsLayoutItemPolygon: src/core/layout/qgslayoutitempolygon.h#L29 -QgsLayoutItemPolyline._addNode: src/core/layout/qgslayoutitempolyline.h#L197 -QgsLayoutItemPolyline._draw: src/core/layout/qgslayoutitempolyline.h#L199 -QgsLayoutItemPolyline._readXmlStyle: src/core/layout/qgslayoutitempolyline.h#L200 -QgsLayoutItemPolyline._removeNode: src/core/layout/qgslayoutitempolyline.h#L198 -QgsLayoutItemPolyline._writeXmlStyle: src/core/layout/qgslayoutitempolyline.h#L201 -QgsLayoutItemPolyline.accept: src/core/layout/qgslayoutitempolyline.h#L193 -QgsLayoutItemPolyline.arrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L170 -QgsLayoutItemPolyline.arrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L156 -QgsLayoutItemPolyline.arrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L191 -QgsLayoutItemPolyline.arrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L121 +QgsLayoutItemPolyline._addNode: src/core/layout/qgslayoutitempolyline.h#L198 +QgsLayoutItemPolyline._draw: src/core/layout/qgslayoutitempolyline.h#L200 +QgsLayoutItemPolyline._readXmlStyle: src/core/layout/qgslayoutitempolyline.h#L201 +QgsLayoutItemPolyline._removeNode: src/core/layout/qgslayoutitempolyline.h#L199 +QgsLayoutItemPolyline._writeXmlStyle: src/core/layout/qgslayoutitempolyline.h#L202 +QgsLayoutItemPolyline.accept: src/core/layout/qgslayoutitempolyline.h#L194 +QgsLayoutItemPolyline.arrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L171 +QgsLayoutItemPolyline.arrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L157 +QgsLayoutItemPolyline.arrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L192 +QgsLayoutItemPolyline.arrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L122 QgsLayoutItemPolyline.create: src/core/layout/qgslayoutitempolyline.h#L63 QgsLayoutItemPolyline.displayName: src/core/layout/qgslayoutitempolyline.h#L67 -QgsLayoutItemPolyline.endMarker: src/core/layout/qgslayoutitempolyline.h#L102 -QgsLayoutItemPolyline.endSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L149 +QgsLayoutItemPolyline.endMarker: src/core/layout/qgslayoutitempolyline.h#L103 +QgsLayoutItemPolyline.endSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L150 QgsLayoutItemPolyline.icon: src/core/layout/qgslayoutitempolyline.h#L66 -QgsLayoutItemPolyline.readPropertiesFromElement: src/core/layout/qgslayoutitempolyline.h#L203 -QgsLayoutItemPolyline.setArrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L177 -QgsLayoutItemPolyline.setArrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L163 -QgsLayoutItemPolyline.setArrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L184 -QgsLayoutItemPolyline.setArrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L115 -QgsLayoutItemPolyline.setEndMarker: src/core/layout/qgslayoutitempolyline.h#L109 -QgsLayoutItemPolyline.setEndSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L142 -QgsLayoutItemPolyline.setStartMarker: src/core/layout/qgslayoutitempolyline.h#L95 -QgsLayoutItemPolyline.setStartSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L128 -QgsLayoutItemPolyline.setSymbol: src/core/layout/qgslayoutitempolyline.h#L81 +QgsLayoutItemPolyline.isValid: src/core/layout/qgslayoutitempolyline.h#L69 +QgsLayoutItemPolyline.readPropertiesFromElement: src/core/layout/qgslayoutitempolyline.h#L204 +QgsLayoutItemPolyline.setArrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L178 +QgsLayoutItemPolyline.setArrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L164 +QgsLayoutItemPolyline.setArrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L185 +QgsLayoutItemPolyline.setArrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L116 +QgsLayoutItemPolyline.setEndMarker: src/core/layout/qgslayoutitempolyline.h#L110 +QgsLayoutItemPolyline.setEndSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L143 +QgsLayoutItemPolyline.setStartMarker: src/core/layout/qgslayoutitempolyline.h#L96 +QgsLayoutItemPolyline.setStartSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L129 +QgsLayoutItemPolyline.setSymbol: src/core/layout/qgslayoutitempolyline.h#L82 QgsLayoutItemPolyline.shape: src/core/layout/qgslayoutitempolyline.h#L68 -QgsLayoutItemPolyline.startMarker: src/core/layout/qgslayoutitempolyline.h#L88 -QgsLayoutItemPolyline.startSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L135 -QgsLayoutItemPolyline.symbol: src/core/layout/qgslayoutitempolyline.h#L74 +QgsLayoutItemPolyline.startMarker: src/core/layout/qgslayoutitempolyline.h#L89 +QgsLayoutItemPolyline.startSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L136 +QgsLayoutItemPolyline.symbol: src/core/layout/qgslayoutitempolyline.h#L75 QgsLayoutItemPolyline.type: src/core/layout/qgslayoutitempolyline.h#L65 -QgsLayoutItemPolyline.updateBoundingRect: src/core/layout/qgslayoutitempolyline.h#L207 -QgsLayoutItemPolyline.writePropertiesToElement: src/core/layout/qgslayoutitempolyline.h#L202 +QgsLayoutItemPolyline.updateBoundingRect: src/core/layout/qgslayoutitempolyline.h#L208 +QgsLayoutItemPolyline.writePropertiesToElement: src/core/layout/qgslayoutitempolyline.h#L203 QgsLayoutItemPolyline: src/core/layout/qgslayoutitempolyline.h#L32 QgsLayoutItemRegistry.addLayoutItemType: src/core/layout/qgslayoutitemregistry.h#L429 QgsLayoutItemRegistry.addLayoutMultiFrameType: src/core/layout/qgslayoutitemregistry.h#L435 @@ -7853,33 +7855,34 @@ QgsLayoutMultiFrameAbstractMetadata.resolvePaths: src/core/layout/qgslayoutitemr QgsLayoutMultiFrameAbstractMetadata.type: src/core/layout/qgslayoutitemregistry.h#L204 QgsLayoutMultiFrameAbstractMetadata.visibleName: src/core/layout/qgslayoutitemregistry.h#L214 QgsLayoutMultiFrameAbstractMetadata: src/core/layout/qgslayoutitemregistry.h#L186 -QgsLayoutNodesItem._addNode: src/core/layout/qgslayoutitemnodeitem.h#L140 -QgsLayoutNodesItem._draw: src/core/layout/qgslayoutitemnodeitem.h#L146 -QgsLayoutNodesItem._readXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L149 -QgsLayoutNodesItem._removeNode: src/core/layout/qgslayoutitemnodeitem.h#L143 -QgsLayoutNodesItem._writeXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L152 +QgsLayoutNodesItem._addNode: src/core/layout/qgslayoutitemnodeitem.h#L149 +QgsLayoutNodesItem._draw: src/core/layout/qgslayoutitemnodeitem.h#L155 +QgsLayoutNodesItem._readXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L158 +QgsLayoutNodesItem._removeNode: src/core/layout/qgslayoutitemnodeitem.h#L152 +QgsLayoutNodesItem._writeXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L161 QgsLayoutNodesItem.addNode: src/core/layout/qgslayoutitemnodeitem.h#L54 QgsLayoutNodesItem.boundingRect: src/core/layout/qgslayoutitemnodeitem.h#L110 -QgsLayoutNodesItem.computeDistance: src/core/layout/qgslayoutitemnodeitem.h#L161 +QgsLayoutNodesItem.computeDistance: src/core/layout/qgslayoutitemnodeitem.h#L170 QgsLayoutNodesItem.deselectNode: src/core/layout/qgslayoutitemnodeitem.h#L107 -QgsLayoutNodesItem.draw: src/core/layout/qgslayoutitemnodeitem.h#L128 +QgsLayoutNodesItem.draw: src/core/layout/qgslayoutitemnodeitem.h#L137 QgsLayoutNodesItem.estimatedFrameBleed: src/core/layout/qgslayoutitemnodeitem.h#L114 -QgsLayoutNodesItem.itemFlags: src/core/layout/qgslayoutitemnodeitem.h#L129 +QgsLayoutNodesItem.isValid: src/core/layout/qgslayoutitemnodeitem.h#L123 +QgsLayoutNodesItem.itemFlags: src/core/layout/qgslayoutitemnodeitem.h#L138 QgsLayoutNodesItem.moveNode: src/core/layout/qgslayoutitemnodeitem.h#L66 QgsLayoutNodesItem.nodeAtPosition: src/core/layout/qgslayoutitemnodeitem.h#L76 QgsLayoutNodesItem.nodePosition: src/core/layout/qgslayoutitemnodeitem.h#L84 QgsLayoutNodesItem.nodes: src/core/layout/qgslayoutitemnodeitem.h#L44 QgsLayoutNodesItem.nodesSize: src/core/layout/qgslayoutitemnodeitem.h#L92 -QgsLayoutNodesItem.readPropertiesFromElement: src/core/layout/qgslayoutitemnodeitem.h#L131 +QgsLayoutNodesItem.readPropertiesFromElement: src/core/layout/qgslayoutitemnodeitem.h#L140 QgsLayoutNodesItem.removeNode: src/core/layout/qgslayoutitemnodeitem.h#L89 -QgsLayoutNodesItem.rescaleToFitBoundingBox: src/core/layout/qgslayoutitemnodeitem.h#L158 +QgsLayoutNodesItem.rescaleToFitBoundingBox: src/core/layout/qgslayoutitemnodeitem.h#L167 QgsLayoutNodesItem.selectedNode: src/core/layout/qgslayoutitemnodeitem.h#L102 QgsLayoutNodesItem.setDisplayNodes: src/core/layout/qgslayoutitemnodeitem.h#L59 QgsLayoutNodesItem.setNodes: src/core/layout/qgslayoutitemnodeitem.h#L38 QgsLayoutNodesItem.setSelectedNode: src/core/layout/qgslayoutitemnodeitem.h#L97 -QgsLayoutNodesItem.updateBoundingRect: src/core/layout/qgslayoutitemnodeitem.h#L175 -QgsLayoutNodesItem.updateSceneRect: src/core/layout/qgslayoutitemnodeitem.h#L164 -QgsLayoutNodesItem.writePropertiesToElement: src/core/layout/qgslayoutitemnodeitem.h#L130 +QgsLayoutNodesItem.updateBoundingRect: src/core/layout/qgslayoutitemnodeitem.h#L184 +QgsLayoutNodesItem.updateSceneRect: src/core/layout/qgslayoutitemnodeitem.h#L173 +QgsLayoutNodesItem.writePropertiesToElement: src/core/layout/qgslayoutitemnodeitem.h#L139 QgsLayoutNodesItem: src/core/layout/qgslayoutitemnodeitem.h#L28 QgsLayoutNorthArrowHandler.arrowRotation: src/core/layout/qgslayoutnortharrowhandler.h#L52 QgsLayoutNorthArrowHandler.arrowRotationChanged: src/core/layout/qgslayoutnortharrowhandler.h#L99 @@ -7906,8 +7909,9 @@ QgsLayoutObject.writeObjectPropertiesToElement: src/core/layout/qgslayoutobject. QgsLayoutObject: src/core/layout/qgslayoutobject.h#L38 QgsLayoutPageCollection.QgsLayoutPageCollection: src/core/layout/qgslayoutpagecollection.h#L49 QgsLayoutPageCollection.addPage: src/core/layout/qgslayoutpagecollection.h#L159 +QgsLayoutPageCollection.applyPropertiesToAllOtherPages: src/core/layout/qgslayoutpagecollection.h#L396 QgsLayoutPageCollection.beginPageSizeChange: src/core/layout/qgslayoutpagecollection.h#L245 -QgsLayoutPageCollection.changed: src/core/layout/qgslayoutpagecollection.h#L403 +QgsLayoutPageCollection.changed: src/core/layout/qgslayoutpagecollection.h#L410 QgsLayoutPageCollection.clear: src/core/layout/qgslayoutpagecollection.h#L216 QgsLayoutPageCollection.deletePage: src/core/layout/qgslayoutpagecollection.h#L200 QgsLayoutPageCollection.deletePage: src/core/layout/qgslayoutpagecollection.h#L210 @@ -7919,7 +7923,7 @@ QgsLayoutPageCollection.layout: src/core/layout/qgslayoutpagecollection.h#L54 QgsLayoutPageCollection.maximumPageSize: src/core/layout/qgslayoutpagecollection.h#L275 QgsLayoutPageCollection.maximumPageWidth: src/core/layout/qgslayoutpagecollection.h#L267 QgsLayoutPageCollection.page: src/core/layout/qgslayoutpagecollection.h#L76 -QgsLayoutPageCollection.pageAboutToBeRemoved: src/core/layout/qgslayoutpagecollection.h#L411 +QgsLayoutPageCollection.pageAboutToBeRemoved: src/core/layout/qgslayoutpagecollection.h#L418 QgsLayoutPageCollection.pageAtPoint: src/core/layout/qgslayoutpagecollection.h#L328 QgsLayoutPageCollection.pageCount: src/core/layout/qgslayoutpagecollection.h#L67 QgsLayoutPageCollection.pageIsEmpty: src/core/layout/qgslayoutpagecollection.h#L113 @@ -7932,7 +7936,7 @@ QgsLayoutPageCollection.pageStyleSymbol: src/core/layout/qgslayoutpagecollection QgsLayoutPageCollection.positionOnPage: src/core/layout/qgslayoutpagecollection.h#L347 QgsLayoutPageCollection.predictPageNumberForPoint: src/core/layout/qgslayoutpagecollection.h#L316 QgsLayoutPageCollection.readXml: src/core/layout/qgslayoutpagecollection.h#L379 -QgsLayoutPageCollection.redraw: src/core/layout/qgslayoutpagecollection.h#L396 +QgsLayoutPageCollection.redraw: src/core/layout/qgslayoutpagecollection.h#L403 QgsLayoutPageCollection.reflow: src/core/layout/qgslayoutpagecollection.h#L259 QgsLayoutPageCollection.resizeToContents: src/core/layout/qgslayoutpagecollection.h#L367 QgsLayoutPageCollection.setPageStyleSymbol: src/core/layout/qgslayoutpagecollection.h#L229 @@ -8395,14 +8399,14 @@ QgsLineSegment2D.startX: src/core/geometry/qgslinesegment.h#L80 QgsLineSegment2D.startY: src/core/geometry/qgslinesegment.h#L90 QgsLineSegment2D: src/core/geometry/qgslinesegment.h#L31 QgsLineString.QgsLineString: src/core/geometry/qgslinestring.h#L257 -QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1159 -QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1100 -QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1082 -QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1128 -QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1050 -QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1021 +QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1169 +QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1110 +QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1092 +QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1138 +QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1060 +QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1031 QgsLineString.addVertex: src/core/geometry/qgslinestring.h#L918 -QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1049 +QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1059 QgsLineString.append: src/core/geometry/qgslinestring.h#L912 QgsLineString.asGml2: src/core/geometry/qgslinestring.h#L982 QgsLineString.asGml3: src/core/geometry/qgslinestring.h#L983 @@ -8412,25 +8416,25 @@ QgsLineString.asWkb: src/core/geometry/qgslinestring.h#L980 QgsLineString.asWkt: src/core/geometry/qgslinestring.h#L981 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L961 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L962 -QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1189 -QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1182 -QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1199 +QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1192 +QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1045 QgsLineString.clear: src/core/geometry/qgslinestring.h#L953 QgsLineString.clone: src/core/geometry/qgslinestring.h#L952 QgsLineString.close: src/core/geometry/qgslinestring.h#L921 -QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1032 -QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1242 -QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1056 -QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1079 -QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1030 -QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1010 -QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1042 +QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1252 +QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1066 +QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1089 +QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1040 +QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1020 +QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1036 QgsLineString.dimension: src/core/geometry/qgslinestring.h#L951 -QgsLineString.draw: src/core/geometry/qgslinestring.h#L1016 -QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1022 -QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1053 -QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1052 -QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1002 +QgsLineString.draw: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1032 +QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1063 +QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1062 +QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1012 QgsLineString.equals: src/core/geometry/qgslinestring.h#L429 QgsLineString.extend: src/core/geometry/qgslinestring.h#L934 QgsLineString.fromBezierCurve: src/core/geometry/qgslinestring.h#L296 @@ -8441,28 +8445,28 @@ QgsLineString.fuzzyDistanceEqual: src/core/geometry/qgslinestring.h#L424 QgsLineString.fuzzyEqual: src/core/geometry/qgslinestring.h#L395 QgsLineString.geometryType: src/core/geometry/qgslinestring.h#L950 QgsLineString.indexOf: src/core/geometry/qgslinestring.h#L955 -QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1024 -QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1212 -QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1034 +QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1222 +QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1039 QgsLineString.isClosed2D: src/core/geometry/qgslinestring.h#L960 QgsLineString.isClosed: src/core/geometry/qgslinestring.h#L959 QgsLineString.isEmpty: src/core/geometry/qgslinestring.h#L954 QgsLineString.isValid: src/core/geometry/qgslinestring.h#L956 -QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1000 +QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1010 QgsLineString.length: src/core/geometry/qgslinestring.h#L988 -QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1238 +QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1248 QgsLineString.mAt: src/core/geometry/qgslinestring.h#L698 -QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1197 -QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1025 -QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1013 -QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1012 -QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1033 +QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1207 +QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1023 +QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1022 +QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1043 QgsLineString.pointN: src/core/geometry/qgslinestring.h#L449 -QgsLineString.points: src/core/geometry/qgslinestring.h#L1014 +QgsLineString.points: src/core/geometry/qgslinestring.h#L1024 QgsLineString.removeDuplicateNodes: src/core/geometry/qgslinestring.h#L958 -QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1028 -QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1059 -QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1048 +QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1038 +QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1069 +QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1058 QgsLineString.setMAt: src/core/geometry/qgslinestring.h#L868 QgsLineString.setPoints: src/core/geometry/qgslinestring.h#L906 QgsLineString.setXAt: src/core/geometry/qgslinestring.h#L739 @@ -8470,14 +8474,14 @@ QgsLineString.setYAt: src/core/geometry/qgslinestring.h#L780 QgsLineString.setZAt: src/core/geometry/qgslinestring.h#L824 QgsLineString.simplifyByDistance: src/core/geometry/qgslinestring.h#L975 QgsLineString.snappedToGrid: src/core/geometry/qgslinestring.h#L957 -QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1001 -QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1045 -QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1054 +QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1011 +QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1055 +QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1064 QgsLineString.toCurveType: src/core/geometry/qgslinestring.h#L927 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1018 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1019 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1058 -QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1047 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1028 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1068 +QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1057 QgsLineString.wkbSize: src/core/geometry/qgslinestring.h#L979 QgsLineString.xAt: src/core/geometry/qgslinestring.h#L481 QgsLineString.yAt: src/core/geometry/qgslinestring.h#L511 @@ -14879,20 +14883,20 @@ QgsRemappingSinkDefinition.setSourceCrs: src/core/qgsremappingproxyfeaturesink.h QgsRemappingSinkDefinition.sourceCrs: src/core/qgsremappingproxyfeaturesink.h#L82 QgsRemappingSinkDefinition.toVariant: src/core/qgsremappingproxyfeaturesink.h#L138 QgsRemappingSinkDefinition: src/core/qgsremappingproxyfeaturesink.h#L38 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L240 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L247 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L241 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L248 QgsRenderChecker.controlImagePath: src/core/qgsrenderchecker.h#L74 -QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L265 +QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L266 QgsRenderChecker.elapsedTime: src/core/qgsrenderchecker.h#L129 -QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L281 -QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L272 +QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L282 +QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L273 QgsRenderChecker.imageToHash: src/core/qgsrenderchecker.h#L156 -QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L259 +QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L260 QgsRenderChecker.markdownReport: src/core/qgsrenderchecker.h#L103 QgsRenderChecker.matchPercent: src/core/qgsrenderchecker.h#L112 QgsRenderChecker.renderedImage: src/core/qgsrenderchecker.h#L175 QgsRenderChecker.report: src/core/qgsrenderchecker.h#L92 -QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L225 +QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L226 QgsRenderChecker.setColorTolerance: src/core/qgsrenderchecker.h#L185 QgsRenderChecker.setControlExtension: src/core/qgsrenderchecker.h#L145 QgsRenderChecker.setControlImagePath: src/core/qgsrenderchecker.h#L82 @@ -14905,7 +14909,7 @@ QgsRenderChecker.setMapSettings: src/core/qgsrenderchecker.h#L177 QgsRenderChecker.setRenderedImage: src/core/qgsrenderchecker.h#L161 QgsRenderChecker.setSizeTolerance: src/core/qgsrenderchecker.h#L192 QgsRenderChecker.shouldGenerateReport: src/core/qgsrenderchecker.h#L65 -QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L296 +QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L297 QgsRenderChecker.testReportDir: src/core/qgsrenderchecker.h#L57 QgsRenderChecker: src/core/qgsrenderchecker.h#L41 QgsRenderContext.addSymbolLayerClipGeometry: src/core/qgsrendercontext.h#L1028 diff --git a/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py b/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py index 012c2ff6e9d..245b0470a35 100644 --- a/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py +++ b/python/PyQt6/gui/additions/qgssettingsenumflageditorwrapper.py @@ -17,7 +17,7 @@ *************************************************************************** """ -from qgis.PyQt.QtWidgets import QWidget, QComboBox +from qgis.PyQt.QtWidgets import QComboBox from qgis.core import QgsSettingsEntryBase from qgis.gui import QgsSettingsEditorWidgetWrapper @@ -46,23 +46,25 @@ class PyQgsSettingsEnumEditorWidgetWrapper(QgsSettingsEditorWidgetWrapper): def setWidgetFromSetting(self): if self.setting: - return self.setWidgetFromVariant(self.setting.value(self.dynamicKeyPartList())) + return self.setWidgetFromVariant(self.setting.valueAsVariant(self.dynamicKeyPartList())) return False def setSettingFromWidget(self): if self.editor: - self.setting.setValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) + self.setting.setVariantValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) return True else: return False def variantValueFromWidget(self): if self.editor: - return self.setting.defaultValue().__class__(self.editor.currentData()) + return self.editor.currentData() return None def setWidgetFromVariant(self, value): - if self.editor: + if self.editor and value is not None: + if isinstance(value, int): + value = self.setting.metaEnum().valueToKey(value) idx = self.editor.findData(value) self.editor.setCurrentIndex(idx) return idx >= 0 @@ -71,7 +73,7 @@ class PyQgsSettingsEnumEditorWidgetWrapper(QgsSettingsEditorWidgetWrapper): def createEditorPrivate(self, parent=None): return QComboBox(parent) - def configureEditorPrivate(self, editor: QWidget, setting: QgsSettingsEntryBase): + def configureEditorPrivate(self, editor: QComboBox, setting: QgsSettingsEntryBase): self.setting = setting if isinstance(editor, QComboBox): self.editor = editor @@ -79,7 +81,7 @@ class PyQgsSettingsEnumEditorWidgetWrapper(QgsSettingsEditorWidgetWrapper): value = self.setting.metaEnum().value(i) key = self.setting.metaEnum().key(i) text = self.displayStrings.get(value, key) - self.editor.addItem(text, value) + self.editor.addItem(text, key) return True else: return False diff --git a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in index 8451b57d139..b3af58e91d9 100644 --- a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in +++ b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in @@ -34,12 +34,19 @@ Adds an editor widget ``wrapper`` to the registry If an editor widget with same id already exists, the wrapper is deleted and ``False`` is returned. %End - QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const; + void addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper /Transfer/, const QgsSettingsEntryBase *setting /KeepReference/ ); +%Docstring +Adds an editor widget ``wrapper`` for a specific setting to the registry + +.. versionadded:: 3.40 +%End + + QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const /Factory/; %Docstring Returns a new instance of the editor widget for the given ``id`` %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /Factory/; + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /TransferBack/; %Docstring Creates an editor widget for the given ``setting`` using the corresponding registered wrapper %End diff --git a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in index 2d5c5fd16fd..7e6bbe1faaa 100644 --- a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in +++ b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in @@ -23,7 +23,7 @@ Base class for settings editor wrappers #include "qgssettingseditorwidgetwrapper.h" %End public: - static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ) /Factory/; + static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ); %Docstring Creates a wrapper from the definition stored in a ``widget`` created by :py:func:`~QgsSettingsEditorWidgetWrapper.createEditor` %End @@ -44,12 +44,12 @@ This id of the type of settings it handles This mostly correspond to the content of :py:class:`Qgis`.SettingsType but it's a string since custom Python implementation are possible. %End - virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0; + virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0 /Factory/; %Docstring Creates a new instance of the editor wrapper so it can be configured for a widget and a setting %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ); + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ) /TransferBack/; %Docstring Creates the editor widget for the given ``setting`` %End @@ -77,7 +77,7 @@ Returns the value from the widget as a variant The wrapper must be configured before calling this medthod %End - virtual void setWidgetFromVariant( const QVariant &value ) const = 0; + virtual bool setWidgetFromVariant( const QVariant &value ) const = 0; %Docstring Sets the ``value`` of the widget The wrapper must be configured before calling this medthod @@ -106,12 +106,12 @@ Returns the dynamic key parts protected: - virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0; + virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0 /TransferBack/; %Docstring Creates the widgets %End - virtual bool configureEditorPrivate( QWidget *editor, const QgsSettingsEntryBase *setting ) = 0; + virtual bool configureEditorPrivate( QWidget *editor /TransferBack/, const QgsSettingsEntryBase *setting /KeepReference/ ) = 0; %Docstring Configures an existing ``editor`` widget %End diff --git a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in index 9f0fb0b0965..92d26f50b7e 100644 --- a/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in +++ b/python/PyQt6/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in @@ -36,7 +36,7 @@ Constructor virtual bool setSettingFromWidget() const = 0; - virtual void setWidgetFromVariant( const QVariant &value ) const; + virtual bool setWidgetFromVariant( const QVariant &value ) const; virtual bool setWidgetValue( const U &value ) const = 0; %Docstring diff --git a/python/PyQt6/gui/class_map.yaml b/python/PyQt6/gui/class_map.yaml index acd1eb675a3..b4f73b7ec9a 100644 --- a/python/PyQt6/gui/class_map.yaml +++ b/python/PyQt6/gui/class_map.yaml @@ -6732,8 +6732,9 @@ QgsSettingsDoubleSpinBoxWrapper.setWidgetValue: src/gui/settings/qgssettingsedit QgsSettingsDoubleSpinBoxWrapper.valueFromWidget: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L285 QgsSettingsDoubleSpinBoxWrapper: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L267 QgsSettingsEditorWidgetRegistry.addWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L46 -QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 -QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L49 +QgsSettingsEditorWidgetRegistry.addWrapperForSetting: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 +QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L58 +QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L55 QgsSettingsEditorWidgetRegistry: src/gui/settings/qgssettingseditorwidgetregistry.h#L35 QgsSettingsEditorWidgetWrapper.configureAutomaticUpdate: src/gui/settings/qgssettingseditorwidgetwrapper.h#L95 QgsSettingsEditorWidgetWrapper.configureEditor: src/gui/settings/qgssettingseditorwidgetwrapper.h#L59 diff --git a/python/core/__init__.py.in b/python/core/__init__.py.in index fc57a867649..8d33ab90ebf 100644 --- a/python/core/__init__.py.in +++ b/python/core/__init__.py.in @@ -662,10 +662,6 @@ except ModuleNotFoundError: QgsGeometry.as_shapely = _geometry_as_shapely - - - - QgsRasterBlock.as_numpy.__doc__ = """ Returns the block data as a numpy array. @@ -675,6 +671,7 @@ If `use_masking` is `True` then the returned array will be a numpy masked array, .. versionadded:: 3.40 """ + QgsRasterLayer.as_numpy.__doc__ = """ Returns the layer data as a numpy array. @@ -686,6 +683,19 @@ If `bands` is provided, only the specified bands will be included in the returne .. versionadded:: 3.40 """ -QgsGeometry.as_numpy.__doc__ = """Returns the geometry data as a numpy array or list of numpy arrays.""" -QgsGeometry.as_shapely.__doc__ = """Returns the geometry data as a shapely object.""" +QgsGeometry.as_numpy.__doc__ = """ +Returns the geometry data as a numpy array or list of numpy arrays. + +:raises QgsNotSupportedException: if numpy is not available on the system + +.. versionadded:: 3.40 +""" + +QgsGeometry.as_shapely.__doc__ = """ +Returns the geometry data as a shapely object. + +:raises QgsNotSupportedException: if shapely is not available on the system + +.. versionadded:: 3.40 +""" diff --git a/python/core/auto_additions/qgslayoutexporter.py b/python/core/auto_additions/qgslayoutexporter.py index e6187b50435..4644872770e 100644 --- a/python/core/auto_additions/qgslayoutexporter.py +++ b/python/core/auto_additions/qgslayoutexporter.py @@ -6,7 +6,7 @@ try: except NameError: pass try: - QgsLayoutExporter.ImageExportSettings.__attribute_docs__ = {'dpi': 'Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.', 'imageSize': "Manual size in pixels for output image. If imageSize is not\nset then it will be automatically calculated based on the\noutput dpi and layout size.\n\nIf cropToContents is ``True`` then imageSize has no effect.\n\nBe careful when specifying manual sizes if pages in the layout\nhave differing sizes! It's likely not going to give a reasonable\noutput in this case, and the automatic dpi-based image size should be\nused instead.", 'cropToContents': 'Set to ``True`` if image should be cropped so only parts of the layout\ncontaining items are exported.', 'cropMargins': 'Crop to content margins, in pixels. These margins will be added\nto the bounds of the exported layout if cropToContents is ``True``.', 'pages': 'List of specific pages to export, or an empty list to\nexport all pages.\n\nPage numbers are 0 index based, so the first page in the\nlayout corresponds to page 0.', 'generateWorldFile': 'Set to ``True`` to generate an external world file alongside\nexported images.', 'exportMetadata': "Indicates whether image export should include metadata generated\nfrom the layout's project's metadata.\n\n.. versionadded:: 3.2", 'flags': 'Layout context flags, which control how the export will be created.', 'predefinedMapScales': 'A list of predefined scales to use with the layout. This is used\nfor maps which are set to the predefined atlas scaling mode.\n\n.. versionadded:: 3.10'} + QgsLayoutExporter.ImageExportSettings.__attribute_docs__ = {'dpi': 'Resolution to export layout at. If dpi <= 0 the default layout dpi will be used.', 'imageSize': "Manual size in pixels for output image. If imageSize is not\nset then it will be automatically calculated based on the\noutput dpi and layout size.\n\nIf cropToContents is ``True`` then imageSize has no effect.\n\nBe careful when specifying manual sizes if pages in the layout\nhave differing sizes! It's likely not going to give a reasonable\noutput in this case, and the automatic dpi-based image size should be\nused instead.", 'cropToContents': 'Set to ``True`` if image should be cropped so only parts of the layout\ncontaining items are exported.', 'cropMargins': 'Crop to content margins, in pixels. These margins will be added\nto the bounds of the exported layout if cropToContents is ``True``.', 'pages': 'List of specific pages to export, or an empty list to\nexport all pages.\n\nPage numbers are 0 index based, so the first page in the\nlayout corresponds to page 0.', 'generateWorldFile': 'Set to ``True`` to generate an external world file alongside\nexported images.', 'exportMetadata': "Indicates whether image export should include metadata generated\nfrom the layout's project's metadata.\n\n.. versionadded:: 3.2", 'flags': 'Layout context flags, which control how the export will be created.', 'predefinedMapScales': 'A list of predefined scales to use with the layout. This is used\nfor maps which are set to the predefined atlas scaling mode.\n\n.. versionadded:: 3.10', 'quality': 'Image quality, typically used for JPEG compression (whose quality ranges from 1 to 100)\nif quality is set to -1, the default quality will be used.\n\n.. versionadded:: 3.42'} QgsLayoutExporter.ImageExportSettings.__doc__ = """Contains settings relating to exporting layouts to raster images""" QgsLayoutExporter.ImageExportSettings.__group__ = ['layout'] except NameError: diff --git a/python/core/auto_additions/qgsrenderchecker.py b/python/core/auto_additions/qgsrenderchecker.py index 0bb885f095a..ba68598300c 100644 --- a/python/core/auto_additions/qgsrenderchecker.py +++ b/python/core/auto_additions/qgsrenderchecker.py @@ -1,11 +1,16 @@ # The following has been generated automatically from src/core/qgsrenderchecker.h # monkey patching scoped based enum QgsRenderChecker.Flag.AvoidExportingRenderedImage.__doc__ = "Avoids exporting rendered images to reports" +QgsRenderChecker.Flag.Silent.__doc__ = "Don't output non-critical messages to console \n.. versionadded:: 3.40" QgsRenderChecker.Flag.__doc__ = """Render checker flags. .. versionadded:: 3.28 * ``AvoidExportingRenderedImage``: Avoids exporting rendered images to reports +* ``Silent``: Don't output non-critical messages to console + + .. versionadded:: 3.40 + """ # -- diff --git a/python/core/auto_additions/qgstemporalutils.py b/python/core/auto_additions/qgstemporalutils.py index 6c0172389aa..19183e9a845 100644 --- a/python/core/auto_additions/qgstemporalutils.py +++ b/python/core/auto_additions/qgstemporalutils.py @@ -1,6 +1,6 @@ # The following has been generated automatically from src/core/qgstemporalutils.h try: - QgsTemporalUtils.AnimationExportSettings.__attribute_docs__ = {'animationRange': 'Dictates the overall temporal range of the animation.', 'frameDuration': 'Duration of individual export frames', 'outputDirectory': 'Destination directory for created image files.', 'fileNameTemplate': "The filename template for exporting the frames.\n\nThis must be in format prefix####.format, where number of\n``# ``characters represents how many 0's should be left-padded to the frame number\ne.g. my###.jpg will create frames my001.jpg, my002.jpg, etc", 'decorations': 'List of decorations to draw onto exported frames.', 'availableTemporalRanges': 'Contains the list of all available temporal ranges which have data available.\n\nThe list can be a list of non-contiguous ranges (i.e. containing gaps)\nwhich together describe the complete range of times which contain data.\n\nThis list is required whenever the :py:class:`QgsUnitTypes`.TemporalIrregularStep interval is used\nfor an animation.\n\n.. versionadded:: 3.30', 'frameRate': 'Target animation frame rate in frames per second.\n\n.. versionadded:: 3.26'} + QgsTemporalUtils.AnimationExportSettings.__attribute_docs__ = {'animationRange': 'Dictates the overall temporal range of the animation.', 'frameDuration': 'Duration of individual export frames', 'outputDirectory': 'Destination directory for created image files.', 'fileNameTemplate': "The filename template for exporting the frames.\n\nThis must be in format ``prefix####.format``, where number of\n````#`` ``characters represents how many 0's should be left-padded to the frame number\ne.g. ``my###.jpg`` will create frames ``my001.jpg``, ``my002.jpg``, etc", 'decorations': 'List of decorations to draw onto exported frames.', 'availableTemporalRanges': 'Contains the list of all available temporal ranges which have data available.\n\nThe list can be a list of non-contiguous ranges (i.e. containing gaps)\nwhich together describe the complete range of times which contain data.\n\nThis list is required whenever the :py:class:`QgsUnitTypes`.TemporalIrregularStep interval is used\nfor an animation.\n\n.. versionadded:: 3.30', 'frameRate': 'Target animation frame rate in frames per second.\n\n.. versionadded:: 3.26'} QgsTemporalUtils.AnimationExportSettings.__doc__ = """Contains settings relating to exporting animations""" except NameError: pass diff --git a/python/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in b/python/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in index 448bf01eb52..662236f27b8 100644 --- a/python/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in +++ b/python/core/auto_generated/auth/qgsauthconfigurationstorage.sip.in @@ -80,7 +80,7 @@ Initializes the storage. .. note:: - The default implementation does nothing and returns ``True``. + The default implementation does nothing and returns ``True``. This method is called by the authentication manager when the storage is added to the manager. %End diff --git a/python/core/auto_generated/geometry/qgsbox3d.sip.in b/python/core/auto_generated/geometry/qgsbox3d.sip.in index 8985b69322a..c3205eeb8b4 100644 --- a/python/core/auto_generated/geometry/qgsbox3d.sip.in +++ b/python/core/auto_generated/geometry/qgsbox3d.sip.in @@ -36,7 +36,7 @@ extent of a geometry or collection of geometries. public: - QgsBox3D( SIP_PYOBJECT x /TypeHint="Optional[Union[QgsPoint, QgsRectangle, float]]"/ = Py_None, SIP_PYOBJECT y /TypeHint="Optional[QgsPoint, float]"/ = Py_None, SIP_PYOBJECT z /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT x2 /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT y2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT z2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT n /TypeHint="Optional[bool]"/ = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double x2 = 0.0, double y2 = 0.0, double z2 = 0.0, bool n = true )]; + QgsBox3D( SIP_PYOBJECT x /TypeHint="Optional[Union[QgsPoint, QgsVector3D, QgsRectangle, float]]"/ = Py_None, SIP_PYOBJECT y /TypeHint="Optional[QgsPoint, QgsVector3D, float]"/ = Py_None, SIP_PYOBJECT z /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT x2 /TypeHint="Optional[Union[bool, float]]"/ = Py_None, SIP_PYOBJECT y2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT z2 /TypeHint="Optional[float]"/ = Py_None, SIP_PYOBJECT n /TypeHint="Optional[bool]"/ = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double x2 = 0.0, double y2 = 0.0, double z2 = 0.0, bool n = true )]; %Docstring Constructor for QgsBox3D which accepts the ranges of x/y/z coordinates. If ``normalize`` is ``False`` then the normalization step will not be applied automatically. @@ -85,6 +85,30 @@ the normalization step will not be applied automatically. } } } + else if ( sipCanConvertToType( a0, sipType_QgsVector3D, SIP_NOT_NONE ) && sipCanConvertToType( a1, sipType_QgsVector3D, SIP_NOT_NONE ) && a3 == Py_None && a4 == Py_None && a5 == Py_None && a6 == Py_None ) + { + int state; + sipIsErr = 0; + + QgsVector3D *corner1 = reinterpret_cast( sipConvertToType( a0, sipType_QgsVector3D, 0, SIP_NOT_NONE, &state, &sipIsErr ) ); + if ( sipIsErr ) + { + sipReleaseType( corner1, sipType_QgsVector3D, state ); + } + else + { + QgsVector3D *corner2 = reinterpret_cast( sipConvertToType( a1, sipType_QgsVector3D, 0, SIP_NOT_NONE, &state, &sipIsErr ) ); + if ( sipIsErr ) + { + sipReleaseType( corner2, sipType_QgsVector3D, state ); + } + else + { + bool n = a2 == Py_None ? true : PyObject_IsTrue( a2 ); + sipCpp = new QgsBox3D( *corner1, *corner2, n ); + } + } + } else if ( ( a0 == Py_None || PyFloat_AsDouble( a0 ) != -1.0 || !PyErr_Occurred() ) && ( a1 == Py_None || PyFloat_AsDouble( a1 ) != -1.0 || !PyErr_Occurred() ) && @@ -375,6 +399,13 @@ If no ``center`` point is specified then the current center of the box will be u Scale the rectangle around a center coordinates. .. versionadded:: 3.26 +%End + + void grow( double delta ); +%Docstring +Grows the box in place by the specified amount in all dimensions. + +.. versionadded:: 3.42 %End bool isNull() const /HoldGIL/; diff --git a/python/core/auto_generated/geometry/qgsgeometry.sip.in b/python/core/auto_generated/geometry/qgsgeometry.sip.in index 449e6c591bb..b43a47c1502 100644 --- a/python/core/auto_generated/geometry/qgsgeometry.sip.in +++ b/python/core/auto_generated/geometry/qgsgeometry.sip.in @@ -2957,7 +2957,7 @@ geometry will retain the same dimensionality as the input geometry. :param maxAngle: maximum angle at node (0-180) at which smoothing will be applied %End - static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0 ) /Factory/; + static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) /Factory/; %Docstring Creates and returns a new geometry engine representing the specified ``geometry`` using ``precision`` on a grid. The ``precision`` argument was added in 3.36. @@ -2972,6 +2972,8 @@ Many methods available in the :py:class:`QgsGeometryEngine` class can benefit fr a large number of spatial relationships will be tested (such as calling :py:func:`~QgsGeometry.intersects`, :py:func:`~QgsGeometry.within`, etc) then the geometry should first be prepared by calling :py:func:`~QgsGeometry.prepareGeometry` before performing the tests. +The ``flags`` argument was added in QGIS 3.40 to allow control over the resultant GEOS geometry. + Example ------------------------------------- diff --git a/python/core/auto_generated/geometry/qgslinestring.sip.in b/python/core/auto_generated/geometry/qgslinestring.sip.in index 1b9a340f1b6..539b29a2112 100644 --- a/python/core/auto_generated/geometry/qgslinestring.sip.in +++ b/python/core/auto_generated/geometry/qgslinestring.sip.in @@ -643,6 +643,17 @@ If ``useZValues`` is ``True`` then z values will also be considered when testing + QVector splitToDisjointXYParts() const /Factory/; +%Docstring +Divides the linestring into parts that don't share any points or lines. + +This method throws away Z and M coordinates. + +The ownership of returned pointers is transferred to the caller. + +.. versionadded:: 3.40 +%End + double length3D() const /HoldGIL/; %Docstring Returns the length in 3D world of the line string. diff --git a/python/core/auto_generated/layout/qgslayoutexporter.sip.in b/python/core/auto_generated/layout/qgslayoutexporter.sip.in index 71e3605c52b..f359aef2279 100644 --- a/python/core/auto_generated/layout/qgslayoutexporter.sip.in +++ b/python/core/auto_generated/layout/qgslayoutexporter.sip.in @@ -24,6 +24,7 @@ Handles rendering and exports of layouts to various formats. + struct PageExportDetails { QString directory; @@ -142,6 +143,9 @@ Returns the rendered image, or a null QImage if the image does not fit into avai QVector predefinedMapScales; + + int quality; + }; ExportResult exportToImage( const QString &filePath, const QgsLayoutExporter::ImageExportSettings &settings ); diff --git a/python/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in b/python/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in index 93a5be09e5c..7d9e0df6dce 100644 --- a/python/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitemnodeitem.sip.in @@ -111,6 +111,15 @@ Deselects any selected nodes. virtual double estimatedFrameBleed() const; + virtual bool isValid() const = 0; +%Docstring +Must be reimplemented in subclasses. +Typically a polyline is valid if it has at least 2 distinct nodes, +while a polygon is valid if it has at least 3 distinct nodes. + +.. versionadded:: 3.40 +%End + protected: QgsLayoutNodesItem( QgsLayout *layout ); diff --git a/python/core/auto_generated/layout/qgslayoutitempolygon.sip.in b/python/core/auto_generated/layout/qgslayoutitempolygon.sip.in index e1486ac9f7b..e41497482b9 100644 --- a/python/core/auto_generated/layout/qgslayoutitempolygon.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitempolygon.sip.in @@ -53,6 +53,8 @@ The caller takes responsibility for deleting the returned object. virtual QgsGeometry clipPath() const; + virtual bool isValid() const; + QgsFillSymbol *symbol(); %Docstring diff --git a/python/core/auto_generated/layout/qgslayoutitempolyline.sip.in b/python/core/auto_generated/layout/qgslayoutitempolyline.sip.in index 56ce00b95ca..4b1c3de519e 100644 --- a/python/core/auto_generated/layout/qgslayoutitempolyline.sip.in +++ b/python/core/auto_generated/layout/qgslayoutitempolyline.sip.in @@ -55,6 +55,8 @@ The caller takes responsibility for deleting the returned object. virtual QPainterPath shape() const; + virtual bool isValid() const; + QgsLineSymbol *symbol(); %Docstring diff --git a/python/core/auto_generated/layout/qgslayoutpagecollection.sip.in b/python/core/auto_generated/layout/qgslayoutpagecollection.sip.in index 0c2509bbb82..e025acb815c 100644 --- a/python/core/auto_generated/layout/qgslayoutpagecollection.sip.in +++ b/python/core/auto_generated/layout/qgslayoutpagecollection.sip.in @@ -368,6 +368,13 @@ Returns a reference to the collection's guide collection, which manages page sna %End + void applyPropertiesToAllOtherPages( int sourcePage ); +%Docstring +Apply the source page properties (size & background color) to all other pages + +.. versionadded:: 3.42 +%End + public slots: void redraw(); diff --git a/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in b/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in index beeac3339fc..3c5fd6b753e 100644 --- a/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in +++ b/python/core/auto_generated/pointcloud/qgspointclouddataprovider.sip.in @@ -301,6 +301,7 @@ Emitted when point cloud generation state is changed protected: + }; /************************************************************************ diff --git a/python/core/auto_generated/qgsrenderchecker.sip.in b/python/core/auto_generated/qgsrenderchecker.sip.in index 67e6c82e541..068cf934c6a 100644 --- a/python/core/auto_generated/qgsrenderchecker.sip.in +++ b/python/core/auto_generated/qgsrenderchecker.sip.in @@ -183,6 +183,7 @@ Sets the largest allowable difference in size between the rendered and the expec enum class Flag { AvoidExportingRenderedImage, + Silent, }; typedef QFlags Flags; diff --git a/python/core/class_map.yaml b/python/core/class_map.yaml index 77a9bfcc6d8..707b90a7ac6 100644 --- a/python/core/class_map.yaml +++ b/python/core/class_map.yaml @@ -1345,66 +1345,66 @@ QgsAuthConfigSslServer.sslPeerVerifyMode: src/core/auth/qgsauthconfig.h#L418 QgsAuthConfigSslServer.sslProtocol: src/core/auth/qgsauthconfig.h#L406 QgsAuthConfigSslServer.version: src/core/auth/qgsauthconfig.h#L435 QgsAuthConfigSslServer: src/core/auth/qgsauthconfig.h#L389 -QgsAuthConfigurationStorage.authMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L164 -QgsAuthConfigurationStorage.authMethodConfigsWithPayload: src/core/auth/qgsauthconfigurationstorage.h#L174 -QgsAuthConfigurationStorage.authSettingExists: src/core/auth/qgsauthconfigurationstorage.h#L241 -QgsAuthConfigurationStorage.authSettingsChanged: src/core/auth/qgsauthconfigurationstorage.h#L524 -QgsAuthConfigurationStorage.capabilities: src/core/auth/qgsauthconfigurationstorage.h#L118 -QgsAuthConfigurationStorage.certAuthorityChanged: src/core/auth/qgsauthconfigurationstorage.h#L542 -QgsAuthConfigurationStorage.certAuthorityExists: src/core/auth/qgsauthconfigurationstorage.h#L394 -QgsAuthConfigurationStorage.certAuthorityIds: src/core/auth/qgsauthconfigurationstorage.h#L378 -QgsAuthConfigurationStorage.certIdentityChanged: src/core/auth/qgsauthconfigurationstorage.h#L537 -QgsAuthConfigurationStorage.certIdentityExists: src/core/auth/qgsauthconfigurationstorage.h#L298 -QgsAuthConfigurationStorage.certIdentityIds: src/core/auth/qgsauthconfigurationstorage.h#L290 -QgsAuthConfigurationStorage.certTrustPolicyExists: src/core/auth/qgsauthconfigurationstorage.h#L449 -QgsAuthConfigurationStorage.clearMasterPasswords: src/core/auth/qgsauthconfigurationstorage.h#L473 -QgsAuthConfigurationStorage.clearMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L488 +QgsAuthConfigurationStorage.authMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L165 +QgsAuthConfigurationStorage.authMethodConfigsWithPayload: src/core/auth/qgsauthconfigurationstorage.h#L175 +QgsAuthConfigurationStorage.authSettingExists: src/core/auth/qgsauthconfigurationstorage.h#L242 +QgsAuthConfigurationStorage.authSettingsChanged: src/core/auth/qgsauthconfigurationstorage.h#L525 +QgsAuthConfigurationStorage.capabilities: src/core/auth/qgsauthconfigurationstorage.h#L119 +QgsAuthConfigurationStorage.certAuthorityChanged: src/core/auth/qgsauthconfigurationstorage.h#L543 +QgsAuthConfigurationStorage.certAuthorityExists: src/core/auth/qgsauthconfigurationstorage.h#L395 +QgsAuthConfigurationStorage.certAuthorityIds: src/core/auth/qgsauthconfigurationstorage.h#L379 +QgsAuthConfigurationStorage.certIdentityChanged: src/core/auth/qgsauthconfigurationstorage.h#L538 +QgsAuthConfigurationStorage.certIdentityExists: src/core/auth/qgsauthconfigurationstorage.h#L299 +QgsAuthConfigurationStorage.certIdentityIds: src/core/auth/qgsauthconfigurationstorage.h#L291 +QgsAuthConfigurationStorage.certTrustPolicyExists: src/core/auth/qgsauthconfigurationstorage.h#L450 +QgsAuthConfigurationStorage.clearMasterPasswords: src/core/auth/qgsauthconfigurationstorage.h#L474 +QgsAuthConfigurationStorage.clearMethodConfigs: src/core/auth/qgsauthconfigurationstorage.h#L489 QgsAuthConfigurationStorage.description: src/core/auth/qgsauthconfigurationstorage.h#L87 -QgsAuthConfigurationStorage.erase: src/core/auth/qgsauthconfigurationstorage.h#L480 +QgsAuthConfigurationStorage.erase: src/core/auth/qgsauthconfigurationstorage.h#L481 QgsAuthConfigurationStorage.id: src/core/auth/qgsauthconfigurationstorage.h#L93 -QgsAuthConfigurationStorage.initialize: src/core/auth/qgsauthconfigurationstorage.h#L102 -QgsAuthConfigurationStorage.isEnabled: src/core/auth/qgsauthconfigurationstorage.h#L138 -QgsAuthConfigurationStorage.isEncrypted: src/core/auth/qgsauthconfigurationstorage.h#L133 -QgsAuthConfigurationStorage.isReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L157 -QgsAuthConfigurationStorage.isReady: src/core/auth/qgsauthconfigurationstorage.h#L113 -QgsAuthConfigurationStorage.lastError: src/core/auth/qgsauthconfigurationstorage.h#L107 -QgsAuthConfigurationStorage.loadAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L225 -QgsAuthConfigurationStorage.loadCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L386 -QgsAuthConfigurationStorage.loadCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L268 -QgsAuthConfigurationStorage.loadCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L433 -QgsAuthConfigurationStorage.loadMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L184 -QgsAuthConfigurationStorage.loadSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L323 -QgsAuthConfigurationStorage.loadSslCertCustomConfigByHost: src/core/auth/qgsauthconfigurationstorage.h#L331 -QgsAuthConfigurationStorage.loggerTag: src/core/auth/qgsauthconfigurationstorage.h#L578 -QgsAuthConfigurationStorage.masterPasswordChanged: src/core/auth/qgsauthconfigurationstorage.h#L519 -QgsAuthConfigurationStorage.messageLog: src/core/auth/qgsauthconfigurationstorage.h#L500 -QgsAuthConfigurationStorage.methodConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L514 -QgsAuthConfigurationStorage.methodConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L208 +QgsAuthConfigurationStorage.initialize: src/core/auth/qgsauthconfigurationstorage.h#L103 +QgsAuthConfigurationStorage.isEnabled: src/core/auth/qgsauthconfigurationstorage.h#L139 +QgsAuthConfigurationStorage.isEncrypted: src/core/auth/qgsauthconfigurationstorage.h#L134 +QgsAuthConfigurationStorage.isReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L158 +QgsAuthConfigurationStorage.isReady: src/core/auth/qgsauthconfigurationstorage.h#L114 +QgsAuthConfigurationStorage.lastError: src/core/auth/qgsauthconfigurationstorage.h#L108 +QgsAuthConfigurationStorage.loadAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L226 +QgsAuthConfigurationStorage.loadCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L387 +QgsAuthConfigurationStorage.loadCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L269 +QgsAuthConfigurationStorage.loadCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L434 +QgsAuthConfigurationStorage.loadMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L185 +QgsAuthConfigurationStorage.loadSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L324 +QgsAuthConfigurationStorage.loadSslCertCustomConfigByHost: src/core/auth/qgsauthconfigurationstorage.h#L332 +QgsAuthConfigurationStorage.loggerTag: src/core/auth/qgsauthconfigurationstorage.h#L579 +QgsAuthConfigurationStorage.masterPasswordChanged: src/core/auth/qgsauthconfigurationstorage.h#L520 +QgsAuthConfigurationStorage.messageLog: src/core/auth/qgsauthconfigurationstorage.h#L501 +QgsAuthConfigurationStorage.methodConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L515 +QgsAuthConfigurationStorage.methodConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L209 QgsAuthConfigurationStorage.name: src/core/auth/qgsauthconfigurationstorage.h#L74 -QgsAuthConfigurationStorage.readOnlyChanged: src/core/auth/qgsauthconfigurationstorage.h#L529 -QgsAuthConfigurationStorage.removeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L233 -QgsAuthConfigurationStorage.removeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L402 -QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L260 -QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L306 -QgsAuthConfigurationStorage.removeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L441 -QgsAuthConfigurationStorage.removeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L200 -QgsAuthConfigurationStorage.removeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L363 -QgsAuthConfigurationStorage.setCapabilities: src/core/auth/qgsauthconfigurationstorage.h#L561 -QgsAuthConfigurationStorage.setEnabled: src/core/auth/qgsauthconfigurationstorage.h#L144 -QgsAuthConfigurationStorage.setError: src/core/auth/qgsauthconfigurationstorage.h#L566 -QgsAuthConfigurationStorage.setReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L151 -QgsAuthConfigurationStorage.sslCertCustomConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L547 -QgsAuthConfigurationStorage.sslCertCustomConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L354 -QgsAuthConfigurationStorage.sslCertCustomConfigIds: src/core/auth/qgsauthconfigurationstorage.h#L345 -QgsAuthConfigurationStorage.sslCertTrustPolicyChanged: src/core/auth/qgsauthconfigurationstorage.h#L552 -QgsAuthConfigurationStorage.storageChanged: src/core/auth/qgsauthconfigurationstorage.h#L509 -QgsAuthConfigurationStorage.storeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L217 -QgsAuthConfigurationStorage.storeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L371 -QgsAuthConfigurationStorage.storeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L252 -QgsAuthConfigurationStorage.storeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L425 -QgsAuthConfigurationStorage.storeMasterPassword: src/core/auth/qgsauthconfigurationstorage.h#L466 -QgsAuthConfigurationStorage.storeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L193 -QgsAuthConfigurationStorage.storeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L314 +QgsAuthConfigurationStorage.readOnlyChanged: src/core/auth/qgsauthconfigurationstorage.h#L530 +QgsAuthConfigurationStorage.removeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L234 +QgsAuthConfigurationStorage.removeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L403 +QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L261 +QgsAuthConfigurationStorage.removeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L307 +QgsAuthConfigurationStorage.removeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L442 +QgsAuthConfigurationStorage.removeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L201 +QgsAuthConfigurationStorage.removeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L364 +QgsAuthConfigurationStorage.setCapabilities: src/core/auth/qgsauthconfigurationstorage.h#L562 +QgsAuthConfigurationStorage.setEnabled: src/core/auth/qgsauthconfigurationstorage.h#L145 +QgsAuthConfigurationStorage.setError: src/core/auth/qgsauthconfigurationstorage.h#L567 +QgsAuthConfigurationStorage.setReadOnly: src/core/auth/qgsauthconfigurationstorage.h#L152 +QgsAuthConfigurationStorage.sslCertCustomConfigChanged: src/core/auth/qgsauthconfigurationstorage.h#L548 +QgsAuthConfigurationStorage.sslCertCustomConfigExists: src/core/auth/qgsauthconfigurationstorage.h#L355 +QgsAuthConfigurationStorage.sslCertCustomConfigIds: src/core/auth/qgsauthconfigurationstorage.h#L346 +QgsAuthConfigurationStorage.sslCertTrustPolicyChanged: src/core/auth/qgsauthconfigurationstorage.h#L553 +QgsAuthConfigurationStorage.storageChanged: src/core/auth/qgsauthconfigurationstorage.h#L510 +QgsAuthConfigurationStorage.storeAuthSetting: src/core/auth/qgsauthconfigurationstorage.h#L218 +QgsAuthConfigurationStorage.storeCertAuthority: src/core/auth/qgsauthconfigurationstorage.h#L372 +QgsAuthConfigurationStorage.storeCertIdentity: src/core/auth/qgsauthconfigurationstorage.h#L253 +QgsAuthConfigurationStorage.storeCertTrustPolicy: src/core/auth/qgsauthconfigurationstorage.h#L426 +QgsAuthConfigurationStorage.storeMasterPassword: src/core/auth/qgsauthconfigurationstorage.h#L467 +QgsAuthConfigurationStorage.storeMethodConfig: src/core/auth/qgsauthconfigurationstorage.h#L194 +QgsAuthConfigurationStorage.storeSslCertCustomConfig: src/core/auth/qgsauthconfigurationstorage.h#L315 QgsAuthConfigurationStorage.type: src/core/auth/qgsauthconfigurationstorage.h#L81 QgsAuthConfigurationStorage: src/core/auth/qgsauthconfigurationstorage.h#L36 QgsAuthConfigurationStorageDb.authDatabaseConnection: src/core/auth/qgsauthconfigurationstoragedb.h#L75 @@ -4884,7 +4884,7 @@ QgsGeometry.Error.hasWhere: src/core/geometry/qgsgeometry.h#L2776 QgsGeometry.Error.what: src/core/geometry/qgsgeometry.h#L2766 QgsGeometry.Error.where: src/core/geometry/qgsgeometry.h#L2771 QgsGeometry.Error: src/core/geometry/qgsgeometry.h#L2747 -QgsGeometry.QVariant: src/core/geometry/qgsgeometry.h#L3183 +QgsGeometry.QVariant: src/core/geometry/qgsgeometry.h#L3185 QgsGeometry.QgsGeometry: src/core/geometry/qgsgeometry.h#L184 QgsGeometry.__repr__: src/core/geometry/qgsgeometry.h#L2185 QgsGeometry.addPart: src/core/geometry/qgsgeometry.h#L930 @@ -4938,8 +4938,8 @@ QgsGeometry.contains: src/core/geometry/qgsgeometry.h#L1465 QgsGeometry.contains: src/core/geometry/qgsgeometry.h#L1472 QgsGeometry.contains: src/core/geometry/qgsgeometry.h#L1483 QgsGeometry.convertGeometryCollectionToSubclass: src/core/geometry/qgsgeometry.h#L2609 -QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3170 -QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3177 +QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3172 +QgsGeometry.convertPointList: src/core/geometry/qgsgeometry.h#L3179 QgsGeometry.convertToCurvedMultiType: src/core/geometry/qgsgeometry.h#L2587 QgsGeometry.convertToCurves: src/core/geometry/qgsgeometry.h#L1674 QgsGeometry.convertToMultiType: src/core/geometry/qgsgeometry.h#L2570 @@ -4947,7 +4947,7 @@ QgsGeometry.convertToSingleType: src/core/geometry/qgsgeometry.h#L2598 QgsGeometry.convertToStraightSegment: src/core/geometry/qgsgeometry.h#L2843 QgsGeometry.convertToType: src/core/geometry/qgsgeometry.h#L2252 QgsGeometry.convexHull: src/core/geometry/qgsgeometry.h#L1804 -QgsGeometry.createGeometryEngine: src/core/geometry/qgsgeometry.h#L3163 +QgsGeometry.createGeometryEngine: src/core/geometry/qgsgeometry.h#L3165 QgsGeometry.createPolygonFromQPolygonF: src/core/geometry/qgsgeometry.h#L2952 QgsGeometry.createPolylineFromQPolygonF: src/core/geometry/qgsgeometry.h#L2943 QgsGeometry.createWedgeBuffer: src/core/geometry/qgsgeometry.h#L347 @@ -6710,29 +6710,29 @@ QgsLayoutEffect.compositionMode: src/core/layout/qgslayouteffect.h#L60 QgsLayoutEffect.draw: src/core/layout/qgslayouteffect.h#L64 QgsLayoutEffect.setCompositionMode: src/core/layout/qgslayouteffect.h#L51 QgsLayoutEffect: src/core/layout/qgslayouteffect.h#L36 -QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L664 -QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L671 -QgsLayoutExporter.containsAdvancedEffects: src/core/layout/qgslayoutexporter.h#L690 -QgsLayoutExporter.errorFile: src/core/layout/qgslayoutexporter.h#L608 -QgsLayoutExporter.errorMessage: src/core/layout/qgslayoutexporter.h#L615 -QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L246 -QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L259 -QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L406 -QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L419 -QgsLayoutExporter.exportToPdfs: src/core/layout/qgslayoutexporter.h#L435 -QgsLayoutExporter.exportToSvg: src/core/layout/qgslayoutexporter.h#L587 +QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L676 +QgsLayoutExporter.computeWorldFileParameters: src/core/layout/qgslayoutexporter.h#L683 +QgsLayoutExporter.containsAdvancedEffects: src/core/layout/qgslayoutexporter.h#L702 +QgsLayoutExporter.errorFile: src/core/layout/qgslayoutexporter.h#L620 +QgsLayoutExporter.errorMessage: src/core/layout/qgslayoutexporter.h#L627 +QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L258 +QgsLayoutExporter.exportToImage: src/core/layout/qgslayoutexporter.h#L271 +QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L418 +QgsLayoutExporter.exportToPdf: src/core/layout/qgslayoutexporter.h#L431 +QgsLayoutExporter.exportToPdfs: src/core/layout/qgslayoutexporter.h#L447 QgsLayoutExporter.exportToSvg: src/core/layout/qgslayoutexporter.h#L599 -QgsLayoutExporter.generateFileName: src/core/layout/qgslayoutexporter.h#L699 -QgsLayoutExporter.georeferenceOutput: src/core/layout/qgslayoutexporter.h#L655 -QgsLayoutExporter.layout: src/core/layout/qgslayoutexporter.h#L90 -QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L480 -QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L489 -QgsLayoutExporter.renderPage: src/core/layout/qgslayoutexporter.h#L100 -QgsLayoutExporter.renderPageToImage: src/core/layout/qgslayoutexporter.h#L120 -QgsLayoutExporter.renderRegion: src/core/layout/qgslayoutexporter.h#L129 -QgsLayoutExporter.renderRegionToImage: src/core/layout/qgslayoutexporter.h#L147 -QgsLayoutExporter.requiresRasterization: src/core/layout/qgslayoutexporter.h#L681 -QgsLayoutExporter: src/core/layout/qgslayoutexporter.h#L50 +QgsLayoutExporter.exportToSvg: src/core/layout/qgslayoutexporter.h#L611 +QgsLayoutExporter.generateFileName: src/core/layout/qgslayoutexporter.h#L711 +QgsLayoutExporter.georeferenceOutput: src/core/layout/qgslayoutexporter.h#L667 +QgsLayoutExporter.layout: src/core/layout/qgslayoutexporter.h#L94 +QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L492 +QgsLayoutExporter.print: src/core/layout/qgslayoutexporter.h#L501 +QgsLayoutExporter.renderPage: src/core/layout/qgslayoutexporter.h#L104 +QgsLayoutExporter.renderPageToImage: src/core/layout/qgslayoutexporter.h#L124 +QgsLayoutExporter.renderRegion: src/core/layout/qgslayoutexporter.h#L133 +QgsLayoutExporter.renderRegionToImage: src/core/layout/qgslayoutexporter.h#L151 +QgsLayoutExporter.requiresRasterization: src/core/layout/qgslayoutexporter.h#L693 +QgsLayoutExporter: src/core/layout/qgslayoutexporter.h#L51 QgsLayoutFrame.cleanup: src/core/layout/qgslayoutframe.h#L54 QgsLayoutFrame.create: src/core/layout/qgslayoutframe.h#L46 QgsLayoutFrame.createExpressionContext: src/core/layout/qgslayoutframe.h#L112 @@ -7567,53 +7567,55 @@ QgsLayoutItemPicture.svgStrokeWidth: src/core/layout/qgslayoutitempicture.h#L209 QgsLayoutItemPicture.type: src/core/layout/qgslayoutitempicture.h#L63 QgsLayoutItemPicture.writePropertiesToElement: src/core/layout/qgslayoutitempicture.h#L313 QgsLayoutItemPicture: src/core/layout/qgslayoutitempicture.h#L34 -QgsLayoutItemPolygon._addNode: src/core/layout/qgslayoutitempolygon.h#L76 -QgsLayoutItemPolygon._draw: src/core/layout/qgslayoutitempolygon.h#L78 -QgsLayoutItemPolygon._readXmlStyle: src/core/layout/qgslayoutitempolygon.h#L79 -QgsLayoutItemPolygon._removeNode: src/core/layout/qgslayoutitempolygon.h#L77 -QgsLayoutItemPolygon._writeXmlStyle: src/core/layout/qgslayoutitempolygon.h#L80 +QgsLayoutItemPolygon._addNode: src/core/layout/qgslayoutitempolygon.h#L77 +QgsLayoutItemPolygon._draw: src/core/layout/qgslayoutitempolygon.h#L79 +QgsLayoutItemPolygon._readXmlStyle: src/core/layout/qgslayoutitempolygon.h#L80 +QgsLayoutItemPolygon._removeNode: src/core/layout/qgslayoutitempolygon.h#L78 +QgsLayoutItemPolygon._writeXmlStyle: src/core/layout/qgslayoutitempolygon.h#L81 QgsLayoutItemPolygon.accept: src/core/layout/qgslayoutitempolygon.h#L58 QgsLayoutItemPolygon.clipPath: src/core/layout/qgslayoutitempolygon.h#L60 QgsLayoutItemPolygon.create: src/core/layout/qgslayoutitempolygon.h#L53 QgsLayoutItemPolygon.displayName: src/core/layout/qgslayoutitempolygon.h#L57 QgsLayoutItemPolygon.icon: src/core/layout/qgslayoutitempolygon.h#L56 +QgsLayoutItemPolygon.isValid: src/core/layout/qgslayoutitempolygon.h#L61 QgsLayoutItemPolygon.itemFlags: src/core/layout/qgslayoutitempolygon.h#L59 -QgsLayoutItemPolygon.setSymbol: src/core/layout/qgslayoutitempolygon.h#L73 -QgsLayoutItemPolygon.symbol: src/core/layout/qgslayoutitempolygon.h#L66 +QgsLayoutItemPolygon.setSymbol: src/core/layout/qgslayoutitempolygon.h#L74 +QgsLayoutItemPolygon.symbol: src/core/layout/qgslayoutitempolygon.h#L67 QgsLayoutItemPolygon.type: src/core/layout/qgslayoutitempolygon.h#L55 QgsLayoutItemPolygon: src/core/layout/qgslayoutitempolygon.h#L29 -QgsLayoutItemPolyline._addNode: src/core/layout/qgslayoutitempolyline.h#L197 -QgsLayoutItemPolyline._draw: src/core/layout/qgslayoutitempolyline.h#L199 -QgsLayoutItemPolyline._readXmlStyle: src/core/layout/qgslayoutitempolyline.h#L200 -QgsLayoutItemPolyline._removeNode: src/core/layout/qgslayoutitempolyline.h#L198 -QgsLayoutItemPolyline._writeXmlStyle: src/core/layout/qgslayoutitempolyline.h#L201 -QgsLayoutItemPolyline.accept: src/core/layout/qgslayoutitempolyline.h#L193 -QgsLayoutItemPolyline.arrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L170 -QgsLayoutItemPolyline.arrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L156 -QgsLayoutItemPolyline.arrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L191 -QgsLayoutItemPolyline.arrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L121 +QgsLayoutItemPolyline._addNode: src/core/layout/qgslayoutitempolyline.h#L198 +QgsLayoutItemPolyline._draw: src/core/layout/qgslayoutitempolyline.h#L200 +QgsLayoutItemPolyline._readXmlStyle: src/core/layout/qgslayoutitempolyline.h#L201 +QgsLayoutItemPolyline._removeNode: src/core/layout/qgslayoutitempolyline.h#L199 +QgsLayoutItemPolyline._writeXmlStyle: src/core/layout/qgslayoutitempolyline.h#L202 +QgsLayoutItemPolyline.accept: src/core/layout/qgslayoutitempolyline.h#L194 +QgsLayoutItemPolyline.arrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L171 +QgsLayoutItemPolyline.arrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L157 +QgsLayoutItemPolyline.arrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L192 +QgsLayoutItemPolyline.arrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L122 QgsLayoutItemPolyline.create: src/core/layout/qgslayoutitempolyline.h#L63 QgsLayoutItemPolyline.displayName: src/core/layout/qgslayoutitempolyline.h#L67 -QgsLayoutItemPolyline.endMarker: src/core/layout/qgslayoutitempolyline.h#L102 -QgsLayoutItemPolyline.endSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L149 +QgsLayoutItemPolyline.endMarker: src/core/layout/qgslayoutitempolyline.h#L103 +QgsLayoutItemPolyline.endSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L150 QgsLayoutItemPolyline.icon: src/core/layout/qgslayoutitempolyline.h#L66 -QgsLayoutItemPolyline.readPropertiesFromElement: src/core/layout/qgslayoutitempolyline.h#L203 -QgsLayoutItemPolyline.setArrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L177 -QgsLayoutItemPolyline.setArrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L163 -QgsLayoutItemPolyline.setArrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L184 -QgsLayoutItemPolyline.setArrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L115 -QgsLayoutItemPolyline.setEndMarker: src/core/layout/qgslayoutitempolyline.h#L109 -QgsLayoutItemPolyline.setEndSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L142 -QgsLayoutItemPolyline.setStartMarker: src/core/layout/qgslayoutitempolyline.h#L95 -QgsLayoutItemPolyline.setStartSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L128 -QgsLayoutItemPolyline.setSymbol: src/core/layout/qgslayoutitempolyline.h#L81 +QgsLayoutItemPolyline.isValid: src/core/layout/qgslayoutitempolyline.h#L69 +QgsLayoutItemPolyline.readPropertiesFromElement: src/core/layout/qgslayoutitempolyline.h#L204 +QgsLayoutItemPolyline.setArrowHeadFillColor: src/core/layout/qgslayoutitempolyline.h#L178 +QgsLayoutItemPolyline.setArrowHeadStrokeColor: src/core/layout/qgslayoutitempolyline.h#L164 +QgsLayoutItemPolyline.setArrowHeadStrokeWidth: src/core/layout/qgslayoutitempolyline.h#L185 +QgsLayoutItemPolyline.setArrowHeadWidth: src/core/layout/qgslayoutitempolyline.h#L116 +QgsLayoutItemPolyline.setEndMarker: src/core/layout/qgslayoutitempolyline.h#L110 +QgsLayoutItemPolyline.setEndSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L143 +QgsLayoutItemPolyline.setStartMarker: src/core/layout/qgslayoutitempolyline.h#L96 +QgsLayoutItemPolyline.setStartSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L129 +QgsLayoutItemPolyline.setSymbol: src/core/layout/qgslayoutitempolyline.h#L82 QgsLayoutItemPolyline.shape: src/core/layout/qgslayoutitempolyline.h#L68 -QgsLayoutItemPolyline.startMarker: src/core/layout/qgslayoutitempolyline.h#L88 -QgsLayoutItemPolyline.startSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L135 -QgsLayoutItemPolyline.symbol: src/core/layout/qgslayoutitempolyline.h#L74 +QgsLayoutItemPolyline.startMarker: src/core/layout/qgslayoutitempolyline.h#L89 +QgsLayoutItemPolyline.startSvgMarkerPath: src/core/layout/qgslayoutitempolyline.h#L136 +QgsLayoutItemPolyline.symbol: src/core/layout/qgslayoutitempolyline.h#L75 QgsLayoutItemPolyline.type: src/core/layout/qgslayoutitempolyline.h#L65 -QgsLayoutItemPolyline.updateBoundingRect: src/core/layout/qgslayoutitempolyline.h#L207 -QgsLayoutItemPolyline.writePropertiesToElement: src/core/layout/qgslayoutitempolyline.h#L202 +QgsLayoutItemPolyline.updateBoundingRect: src/core/layout/qgslayoutitempolyline.h#L208 +QgsLayoutItemPolyline.writePropertiesToElement: src/core/layout/qgslayoutitempolyline.h#L203 QgsLayoutItemPolyline: src/core/layout/qgslayoutitempolyline.h#L32 QgsLayoutItemRegistry.addLayoutItemType: src/core/layout/qgslayoutitemregistry.h#L429 QgsLayoutItemRegistry.addLayoutMultiFrameType: src/core/layout/qgslayoutitemregistry.h#L435 @@ -7853,33 +7855,34 @@ QgsLayoutMultiFrameAbstractMetadata.resolvePaths: src/core/layout/qgslayoutitemr QgsLayoutMultiFrameAbstractMetadata.type: src/core/layout/qgslayoutitemregistry.h#L204 QgsLayoutMultiFrameAbstractMetadata.visibleName: src/core/layout/qgslayoutitemregistry.h#L214 QgsLayoutMultiFrameAbstractMetadata: src/core/layout/qgslayoutitemregistry.h#L186 -QgsLayoutNodesItem._addNode: src/core/layout/qgslayoutitemnodeitem.h#L140 -QgsLayoutNodesItem._draw: src/core/layout/qgslayoutitemnodeitem.h#L146 -QgsLayoutNodesItem._readXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L149 -QgsLayoutNodesItem._removeNode: src/core/layout/qgslayoutitemnodeitem.h#L143 -QgsLayoutNodesItem._writeXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L152 +QgsLayoutNodesItem._addNode: src/core/layout/qgslayoutitemnodeitem.h#L149 +QgsLayoutNodesItem._draw: src/core/layout/qgslayoutitemnodeitem.h#L155 +QgsLayoutNodesItem._readXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L158 +QgsLayoutNodesItem._removeNode: src/core/layout/qgslayoutitemnodeitem.h#L152 +QgsLayoutNodesItem._writeXmlStyle: src/core/layout/qgslayoutitemnodeitem.h#L161 QgsLayoutNodesItem.addNode: src/core/layout/qgslayoutitemnodeitem.h#L54 QgsLayoutNodesItem.boundingRect: src/core/layout/qgslayoutitemnodeitem.h#L110 -QgsLayoutNodesItem.computeDistance: src/core/layout/qgslayoutitemnodeitem.h#L161 +QgsLayoutNodesItem.computeDistance: src/core/layout/qgslayoutitemnodeitem.h#L170 QgsLayoutNodesItem.deselectNode: src/core/layout/qgslayoutitemnodeitem.h#L107 -QgsLayoutNodesItem.draw: src/core/layout/qgslayoutitemnodeitem.h#L128 +QgsLayoutNodesItem.draw: src/core/layout/qgslayoutitemnodeitem.h#L137 QgsLayoutNodesItem.estimatedFrameBleed: src/core/layout/qgslayoutitemnodeitem.h#L114 -QgsLayoutNodesItem.itemFlags: src/core/layout/qgslayoutitemnodeitem.h#L129 +QgsLayoutNodesItem.isValid: src/core/layout/qgslayoutitemnodeitem.h#L123 +QgsLayoutNodesItem.itemFlags: src/core/layout/qgslayoutitemnodeitem.h#L138 QgsLayoutNodesItem.moveNode: src/core/layout/qgslayoutitemnodeitem.h#L66 QgsLayoutNodesItem.nodeAtPosition: src/core/layout/qgslayoutitemnodeitem.h#L76 QgsLayoutNodesItem.nodePosition: src/core/layout/qgslayoutitemnodeitem.h#L84 QgsLayoutNodesItem.nodes: src/core/layout/qgslayoutitemnodeitem.h#L44 QgsLayoutNodesItem.nodesSize: src/core/layout/qgslayoutitemnodeitem.h#L92 -QgsLayoutNodesItem.readPropertiesFromElement: src/core/layout/qgslayoutitemnodeitem.h#L131 +QgsLayoutNodesItem.readPropertiesFromElement: src/core/layout/qgslayoutitemnodeitem.h#L140 QgsLayoutNodesItem.removeNode: src/core/layout/qgslayoutitemnodeitem.h#L89 -QgsLayoutNodesItem.rescaleToFitBoundingBox: src/core/layout/qgslayoutitemnodeitem.h#L158 +QgsLayoutNodesItem.rescaleToFitBoundingBox: src/core/layout/qgslayoutitemnodeitem.h#L167 QgsLayoutNodesItem.selectedNode: src/core/layout/qgslayoutitemnodeitem.h#L102 QgsLayoutNodesItem.setDisplayNodes: src/core/layout/qgslayoutitemnodeitem.h#L59 QgsLayoutNodesItem.setNodes: src/core/layout/qgslayoutitemnodeitem.h#L38 QgsLayoutNodesItem.setSelectedNode: src/core/layout/qgslayoutitemnodeitem.h#L97 -QgsLayoutNodesItem.updateBoundingRect: src/core/layout/qgslayoutitemnodeitem.h#L175 -QgsLayoutNodesItem.updateSceneRect: src/core/layout/qgslayoutitemnodeitem.h#L164 -QgsLayoutNodesItem.writePropertiesToElement: src/core/layout/qgslayoutitemnodeitem.h#L130 +QgsLayoutNodesItem.updateBoundingRect: src/core/layout/qgslayoutitemnodeitem.h#L184 +QgsLayoutNodesItem.updateSceneRect: src/core/layout/qgslayoutitemnodeitem.h#L173 +QgsLayoutNodesItem.writePropertiesToElement: src/core/layout/qgslayoutitemnodeitem.h#L139 QgsLayoutNodesItem: src/core/layout/qgslayoutitemnodeitem.h#L28 QgsLayoutNorthArrowHandler.arrowRotation: src/core/layout/qgslayoutnortharrowhandler.h#L52 QgsLayoutNorthArrowHandler.arrowRotationChanged: src/core/layout/qgslayoutnortharrowhandler.h#L99 @@ -7906,8 +7909,9 @@ QgsLayoutObject.writeObjectPropertiesToElement: src/core/layout/qgslayoutobject. QgsLayoutObject: src/core/layout/qgslayoutobject.h#L38 QgsLayoutPageCollection.QgsLayoutPageCollection: src/core/layout/qgslayoutpagecollection.h#L49 QgsLayoutPageCollection.addPage: src/core/layout/qgslayoutpagecollection.h#L159 +QgsLayoutPageCollection.applyPropertiesToAllOtherPages: src/core/layout/qgslayoutpagecollection.h#L396 QgsLayoutPageCollection.beginPageSizeChange: src/core/layout/qgslayoutpagecollection.h#L245 -QgsLayoutPageCollection.changed: src/core/layout/qgslayoutpagecollection.h#L403 +QgsLayoutPageCollection.changed: src/core/layout/qgslayoutpagecollection.h#L410 QgsLayoutPageCollection.clear: src/core/layout/qgslayoutpagecollection.h#L216 QgsLayoutPageCollection.deletePage: src/core/layout/qgslayoutpagecollection.h#L200 QgsLayoutPageCollection.deletePage: src/core/layout/qgslayoutpagecollection.h#L210 @@ -7919,7 +7923,7 @@ QgsLayoutPageCollection.layout: src/core/layout/qgslayoutpagecollection.h#L54 QgsLayoutPageCollection.maximumPageSize: src/core/layout/qgslayoutpagecollection.h#L275 QgsLayoutPageCollection.maximumPageWidth: src/core/layout/qgslayoutpagecollection.h#L267 QgsLayoutPageCollection.page: src/core/layout/qgslayoutpagecollection.h#L76 -QgsLayoutPageCollection.pageAboutToBeRemoved: src/core/layout/qgslayoutpagecollection.h#L411 +QgsLayoutPageCollection.pageAboutToBeRemoved: src/core/layout/qgslayoutpagecollection.h#L418 QgsLayoutPageCollection.pageAtPoint: src/core/layout/qgslayoutpagecollection.h#L328 QgsLayoutPageCollection.pageCount: src/core/layout/qgslayoutpagecollection.h#L67 QgsLayoutPageCollection.pageIsEmpty: src/core/layout/qgslayoutpagecollection.h#L113 @@ -7932,7 +7936,7 @@ QgsLayoutPageCollection.pageStyleSymbol: src/core/layout/qgslayoutpagecollection QgsLayoutPageCollection.positionOnPage: src/core/layout/qgslayoutpagecollection.h#L347 QgsLayoutPageCollection.predictPageNumberForPoint: src/core/layout/qgslayoutpagecollection.h#L316 QgsLayoutPageCollection.readXml: src/core/layout/qgslayoutpagecollection.h#L379 -QgsLayoutPageCollection.redraw: src/core/layout/qgslayoutpagecollection.h#L396 +QgsLayoutPageCollection.redraw: src/core/layout/qgslayoutpagecollection.h#L403 QgsLayoutPageCollection.reflow: src/core/layout/qgslayoutpagecollection.h#L259 QgsLayoutPageCollection.resizeToContents: src/core/layout/qgslayoutpagecollection.h#L367 QgsLayoutPageCollection.setPageStyleSymbol: src/core/layout/qgslayoutpagecollection.h#L229 @@ -8395,14 +8399,14 @@ QgsLineSegment2D.startX: src/core/geometry/qgslinesegment.h#L80 QgsLineSegment2D.startY: src/core/geometry/qgslinesegment.h#L90 QgsLineSegment2D: src/core/geometry/qgslinesegment.h#L31 QgsLineString.QgsLineString: src/core/geometry/qgslinestring.h#L257 -QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1159 -QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1100 -QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1082 -QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1128 -QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1050 -QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1021 +QgsLineString.__delitem__: src/core/geometry/qgslinestring.h#L1169 +QgsLineString.__getitem__: src/core/geometry/qgslinestring.h#L1110 +QgsLineString.__repr__: src/core/geometry/qgslinestring.h#L1092 +QgsLineString.__setitem__: src/core/geometry/qgslinestring.h#L1138 +QgsLineString.addMValue: src/core/geometry/qgslinestring.h#L1060 +QgsLineString.addToPainterPath: src/core/geometry/qgslinestring.h#L1031 QgsLineString.addVertex: src/core/geometry/qgslinestring.h#L918 -QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1049 +QgsLineString.addZValue: src/core/geometry/qgslinestring.h#L1059 QgsLineString.append: src/core/geometry/qgslinestring.h#L912 QgsLineString.asGml2: src/core/geometry/qgslinestring.h#L982 QgsLineString.asGml3: src/core/geometry/qgslinestring.h#L983 @@ -8412,25 +8416,25 @@ QgsLineString.asWkb: src/core/geometry/qgslinestring.h#L980 QgsLineString.asWkt: src/core/geometry/qgslinestring.h#L981 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L961 QgsLineString.boundingBoxIntersects: src/core/geometry/qgslinestring.h#L962 -QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1189 -QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1182 -QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.calculateBoundingBox3D: src/core/geometry/qgslinestring.h#L1199 +QgsLineString.calculateBoundingBox3d: src/core/geometry/qgslinestring.h#L1192 +QgsLineString.centroid: src/core/geometry/qgslinestring.h#L1045 QgsLineString.clear: src/core/geometry/qgslinestring.h#L953 QgsLineString.clone: src/core/geometry/qgslinestring.h#L952 QgsLineString.close: src/core/geometry/qgslinestring.h#L921 -QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1032 -QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1242 -QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1056 -QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1079 -QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1030 -QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1010 -QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.closestSegment: src/core/geometry/qgslinestring.h#L1042 +QgsLineString.compareToSameClass: src/core/geometry/qgslinestring.h#L1252 +QgsLineString.convertTo: src/core/geometry/qgslinestring.h#L1066 +QgsLineString.createEmptyWithSameType: src/core/geometry/qgslinestring.h#L1089 +QgsLineString.curveSubstring: src/core/geometry/qgslinestring.h#L1040 +QgsLineString.curveToLine: src/core/geometry/qgslinestring.h#L1020 +QgsLineString.deleteVertex: src/core/geometry/qgslinestring.h#L1036 QgsLineString.dimension: src/core/geometry/qgslinestring.h#L951 -QgsLineString.draw: src/core/geometry/qgslinestring.h#L1016 -QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1022 -QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1053 -QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1052 -QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1002 +QgsLineString.draw: src/core/geometry/qgslinestring.h#L1026 +QgsLineString.drawAsPolygon: src/core/geometry/qgslinestring.h#L1032 +QgsLineString.dropMValue: src/core/geometry/qgslinestring.h#L1063 +QgsLineString.dropZValue: src/core/geometry/qgslinestring.h#L1062 +QgsLineString.endPoint: src/core/geometry/qgslinestring.h#L1012 QgsLineString.equals: src/core/geometry/qgslinestring.h#L429 QgsLineString.extend: src/core/geometry/qgslinestring.h#L934 QgsLineString.fromBezierCurve: src/core/geometry/qgslinestring.h#L296 @@ -8441,28 +8445,28 @@ QgsLineString.fuzzyDistanceEqual: src/core/geometry/qgslinestring.h#L424 QgsLineString.fuzzyEqual: src/core/geometry/qgslinestring.h#L395 QgsLineString.geometryType: src/core/geometry/qgslinestring.h#L950 QgsLineString.indexOf: src/core/geometry/qgslinestring.h#L955 -QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1024 -QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1212 -QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.insertVertex: src/core/geometry/qgslinestring.h#L1034 +QgsLineString.interpolateM: src/core/geometry/qgslinestring.h#L1222 +QgsLineString.interpolatePoint: src/core/geometry/qgslinestring.h#L1039 QgsLineString.isClosed2D: src/core/geometry/qgslinestring.h#L960 QgsLineString.isClosed: src/core/geometry/qgslinestring.h#L959 QgsLineString.isEmpty: src/core/geometry/qgslinestring.h#L954 QgsLineString.isValid: src/core/geometry/qgslinestring.h#L956 -QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1000 +QgsLineString.length3D: src/core/geometry/qgslinestring.h#L1010 QgsLineString.length: src/core/geometry/qgslinestring.h#L988 -QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1238 +QgsLineString.lineLocatePointByM: src/core/geometry/qgslinestring.h#L1248 QgsLineString.mAt: src/core/geometry/qgslinestring.h#L698 -QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1197 -QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1025 -QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1013 -QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1012 -QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1033 +QgsLineString.measuredLine: src/core/geometry/qgslinestring.h#L1207 +QgsLineString.moveVertex: src/core/geometry/qgslinestring.h#L1035 +QgsLineString.nCoordinates: src/core/geometry/qgslinestring.h#L1023 +QgsLineString.numPoints: src/core/geometry/qgslinestring.h#L1022 +QgsLineString.pointAt: src/core/geometry/qgslinestring.h#L1043 QgsLineString.pointN: src/core/geometry/qgslinestring.h#L449 -QgsLineString.points: src/core/geometry/qgslinestring.h#L1014 +QgsLineString.points: src/core/geometry/qgslinestring.h#L1024 QgsLineString.removeDuplicateNodes: src/core/geometry/qgslinestring.h#L958 -QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1028 -QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1059 -QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1048 +QgsLineString.reversed: src/core/geometry/qgslinestring.h#L1038 +QgsLineString.scroll: src/core/geometry/qgslinestring.h#L1069 +QgsLineString.segmentLength: src/core/geometry/qgslinestring.h#L1058 QgsLineString.setMAt: src/core/geometry/qgslinestring.h#L868 QgsLineString.setPoints: src/core/geometry/qgslinestring.h#L906 QgsLineString.setXAt: src/core/geometry/qgslinestring.h#L739 @@ -8470,14 +8474,14 @@ QgsLineString.setYAt: src/core/geometry/qgslinestring.h#L780 QgsLineString.setZAt: src/core/geometry/qgslinestring.h#L824 QgsLineString.simplifyByDistance: src/core/geometry/qgslinestring.h#L975 QgsLineString.snappedToGrid: src/core/geometry/qgslinestring.h#L957 -QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1001 -QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1045 -QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1054 +QgsLineString.startPoint: src/core/geometry/qgslinestring.h#L1011 +QgsLineString.sumUpArea: src/core/geometry/qgslinestring.h#L1055 +QgsLineString.swapXy: src/core/geometry/qgslinestring.h#L1064 QgsLineString.toCurveType: src/core/geometry/qgslinestring.h#L927 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1018 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1019 -QgsLineString.transform: src/core/geometry/qgslinestring.h#L1058 -QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1047 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1028 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1029 +QgsLineString.transform: src/core/geometry/qgslinestring.h#L1068 +QgsLineString.vertexAngle: src/core/geometry/qgslinestring.h#L1057 QgsLineString.wkbSize: src/core/geometry/qgslinestring.h#L979 QgsLineString.xAt: src/core/geometry/qgslinestring.h#L481 QgsLineString.yAt: src/core/geometry/qgslinestring.h#L511 @@ -14879,20 +14883,20 @@ QgsRemappingSinkDefinition.setSourceCrs: src/core/qgsremappingproxyfeaturesink.h QgsRemappingSinkDefinition.sourceCrs: src/core/qgsremappingproxyfeaturesink.h#L82 QgsRemappingSinkDefinition.toVariant: src/core/qgsremappingproxyfeaturesink.h#L138 QgsRemappingSinkDefinition: src/core/qgsremappingproxyfeaturesink.h#L38 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L240 -QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L247 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L241 +QgsRenderChecker.compareImages: src/core/qgsrenderchecker.h#L248 QgsRenderChecker.controlImagePath: src/core/qgsrenderchecker.h#L74 -QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L265 +QgsRenderChecker.drawBackground: src/core/qgsrenderchecker.h#L266 QgsRenderChecker.elapsedTime: src/core/qgsrenderchecker.h#L129 -QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L281 -QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L272 +QgsRenderChecker.enableDashBuffering: src/core/qgsrenderchecker.h#L282 +QgsRenderChecker.expectedImageFile: src/core/qgsrenderchecker.h#L273 QgsRenderChecker.imageToHash: src/core/qgsrenderchecker.h#L156 -QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L259 +QgsRenderChecker.isKnownAnomaly: src/core/qgsrenderchecker.h#L260 QgsRenderChecker.markdownReport: src/core/qgsrenderchecker.h#L103 QgsRenderChecker.matchPercent: src/core/qgsrenderchecker.h#L112 QgsRenderChecker.renderedImage: src/core/qgsrenderchecker.h#L175 QgsRenderChecker.report: src/core/qgsrenderchecker.h#L92 -QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L225 +QgsRenderChecker.runTest: src/core/qgsrenderchecker.h#L226 QgsRenderChecker.setColorTolerance: src/core/qgsrenderchecker.h#L185 QgsRenderChecker.setControlExtension: src/core/qgsrenderchecker.h#L145 QgsRenderChecker.setControlImagePath: src/core/qgsrenderchecker.h#L82 @@ -14905,7 +14909,7 @@ QgsRenderChecker.setMapSettings: src/core/qgsrenderchecker.h#L177 QgsRenderChecker.setRenderedImage: src/core/qgsrenderchecker.h#L161 QgsRenderChecker.setSizeTolerance: src/core/qgsrenderchecker.h#L192 QgsRenderChecker.shouldGenerateReport: src/core/qgsrenderchecker.h#L65 -QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L296 +QgsRenderChecker.sourcePath: src/core/qgsrenderchecker.h#L297 QgsRenderChecker.testReportDir: src/core/qgsrenderchecker.h#L57 QgsRenderChecker: src/core/qgsrenderchecker.h#L41 QgsRenderContext.addSymbolLayerClipGeometry: src/core/qgsrendercontext.h#L1028 diff --git a/python/gui/additions/qgssettingsenumflageditorwrapper.py b/python/gui/additions/qgssettingsenumflageditorwrapper.py index 012c2ff6e9d..245b0470a35 100644 --- a/python/gui/additions/qgssettingsenumflageditorwrapper.py +++ b/python/gui/additions/qgssettingsenumflageditorwrapper.py @@ -17,7 +17,7 @@ *************************************************************************** """ -from qgis.PyQt.QtWidgets import QWidget, QComboBox +from qgis.PyQt.QtWidgets import QComboBox from qgis.core import QgsSettingsEntryBase from qgis.gui import QgsSettingsEditorWidgetWrapper @@ -46,23 +46,25 @@ class PyQgsSettingsEnumEditorWidgetWrapper(QgsSettingsEditorWidgetWrapper): def setWidgetFromSetting(self): if self.setting: - return self.setWidgetFromVariant(self.setting.value(self.dynamicKeyPartList())) + return self.setWidgetFromVariant(self.setting.valueAsVariant(self.dynamicKeyPartList())) return False def setSettingFromWidget(self): if self.editor: - self.setting.setValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) + self.setting.setVariantValue(self.variantValueFromWidget(), self.dynamicKeyPartList()) return True else: return False def variantValueFromWidget(self): if self.editor: - return self.setting.defaultValue().__class__(self.editor.currentData()) + return self.editor.currentData() return None def setWidgetFromVariant(self, value): - if self.editor: + if self.editor and value is not None: + if isinstance(value, int): + value = self.setting.metaEnum().valueToKey(value) idx = self.editor.findData(value) self.editor.setCurrentIndex(idx) return idx >= 0 @@ -71,7 +73,7 @@ class PyQgsSettingsEnumEditorWidgetWrapper(QgsSettingsEditorWidgetWrapper): def createEditorPrivate(self, parent=None): return QComboBox(parent) - def configureEditorPrivate(self, editor: QWidget, setting: QgsSettingsEntryBase): + def configureEditorPrivate(self, editor: QComboBox, setting: QgsSettingsEntryBase): self.setting = setting if isinstance(editor, QComboBox): self.editor = editor @@ -79,7 +81,7 @@ class PyQgsSettingsEnumEditorWidgetWrapper(QgsSettingsEditorWidgetWrapper): value = self.setting.metaEnum().value(i) key = self.setting.metaEnum().key(i) text = self.displayStrings.get(value, key) - self.editor.addItem(text, value) + self.editor.addItem(text, key) return True else: return False diff --git a/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in b/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in index 8451b57d139..b3af58e91d9 100644 --- a/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in +++ b/python/gui/auto_generated/settings/qgssettingseditorwidgetregistry.sip.in @@ -34,12 +34,19 @@ Adds an editor widget ``wrapper`` to the registry If an editor widget with same id already exists, the wrapper is deleted and ``False`` is returned. %End - QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const; + void addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper /Transfer/, const QgsSettingsEntryBase *setting /KeepReference/ ); +%Docstring +Adds an editor widget ``wrapper`` for a specific setting to the registry + +.. versionadded:: 3.40 +%End + + QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const /Factory/; %Docstring Returns a new instance of the editor widget for the given ``id`` %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /Factory/; + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = 0 ) const /TransferBack/; %Docstring Creates an editor widget for the given ``setting`` using the corresponding registered wrapper %End diff --git a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in index 2d5c5fd16fd..7e6bbe1faaa 100644 --- a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in +++ b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapper.sip.in @@ -23,7 +23,7 @@ Base class for settings editor wrappers #include "qgssettingseditorwidgetwrapper.h" %End public: - static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ) /Factory/; + static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ); %Docstring Creates a wrapper from the definition stored in a ``widget`` created by :py:func:`~QgsSettingsEditorWidgetWrapper.createEditor` %End @@ -44,12 +44,12 @@ This id of the type of settings it handles This mostly correspond to the content of :py:class:`Qgis`.SettingsType but it's a string since custom Python implementation are possible. %End - virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0; + virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = 0 ) const = 0 /Factory/; %Docstring Creates a new instance of the editor wrapper so it can be configured for a widget and a setting %End - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ); + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = 0 ) /TransferBack/; %Docstring Creates the editor widget for the given ``setting`` %End @@ -77,7 +77,7 @@ Returns the value from the widget as a variant The wrapper must be configured before calling this medthod %End - virtual void setWidgetFromVariant( const QVariant &value ) const = 0; + virtual bool setWidgetFromVariant( const QVariant &value ) const = 0; %Docstring Sets the ``value`` of the widget The wrapper must be configured before calling this medthod @@ -106,12 +106,12 @@ Returns the dynamic key parts protected: - virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0; + virtual QWidget *createEditorPrivate( QWidget *parent = 0 ) const = 0 /TransferBack/; %Docstring Creates the widgets %End - virtual bool configureEditorPrivate( QWidget *editor, const QgsSettingsEntryBase *setting ) = 0; + virtual bool configureEditorPrivate( QWidget *editor /TransferBack/, const QgsSettingsEntryBase *setting /KeepReference/ ) = 0; %Docstring Configures an existing ``editor`` widget %End diff --git a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in index 5120c0f440b..a81c81f014e 100644 --- a/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in +++ b/python/gui/auto_generated/settings/qgssettingseditorwidgetwrapperimpl.sip.in @@ -36,7 +36,7 @@ Constructor virtual bool setSettingFromWidget() const = 0; - virtual void setWidgetFromVariant( const QVariant &value ) const; + virtual bool setWidgetFromVariant( const QVariant &value ) const; virtual bool setWidgetValue( const U &value ) const = 0; %Docstring diff --git a/python/gui/class_map.yaml b/python/gui/class_map.yaml index acd1eb675a3..b4f73b7ec9a 100644 --- a/python/gui/class_map.yaml +++ b/python/gui/class_map.yaml @@ -6732,8 +6732,9 @@ QgsSettingsDoubleSpinBoxWrapper.setWidgetValue: src/gui/settings/qgssettingsedit QgsSettingsDoubleSpinBoxWrapper.valueFromWidget: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L285 QgsSettingsDoubleSpinBoxWrapper: src/gui/settings/qgssettingseditorwidgetwrapperimpl.h#L267 QgsSettingsEditorWidgetRegistry.addWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L46 -QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 -QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L49 +QgsSettingsEditorWidgetRegistry.addWrapperForSetting: src/gui/settings/qgssettingseditorwidgetregistry.h#L52 +QgsSettingsEditorWidgetRegistry.createEditor: src/gui/settings/qgssettingseditorwidgetregistry.h#L58 +QgsSettingsEditorWidgetRegistry.createWrapper: src/gui/settings/qgssettingseditorwidgetregistry.h#L55 QgsSettingsEditorWidgetRegistry: src/gui/settings/qgssettingseditorwidgetregistry.h#L35 QgsSettingsEditorWidgetWrapper.configureAutomaticUpdate: src/gui/settings/qgssettingseditorwidgetwrapper.h#L95 QgsSettingsEditorWidgetWrapper.configureEditor: src/gui/settings/qgssettingseditorwidgetwrapper.h#L59 diff --git a/python/plugins/grassprovider/grass_utils.py b/python/plugins/grassprovider/grass_utils.py index 7d98aee3ee3..37c784c09d7 100644 --- a/python/plugins/grassprovider/grass_utils.py +++ b/python/plugins/grassprovider/grass_utils.py @@ -463,7 +463,9 @@ class GrassUtils: if 'r.out' in line or 'v.out' in line: grassOutDone = True loglines.append(line) - if any([l in line for l in ['WARNING', 'ERROR']]): + if any([l in line for l in ['WARNING', GrassUtils.tr('WARNING')]]): + feedback.pushWarning(line.strip()) + elif any([l in line for l in ['ERROR', GrassUtils.tr('ERROR')]]): feedback.reportError(line.strip()) elif 'Segmentation fault' in line: feedback.reportError(line.strip()) diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index f3eec6ec0d2..fdac9c28624 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -29,9 +29,10 @@ if (HAVE_OPENCL) endif() # Server, also install server subdirectory +set(SERVER_DEST_RESOURCE_FILES "") if (WITH_SERVER) file(GLOB_RECURSE SERVER_RESOURCE_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} server/*) - set(RESOURCES_FILES ${RESOURCES_FILES} ${SERVER_RESOURCE_FILES}) + add_qgis_resources("${CMAKE_CURRENT_SOURCE_DIR}" resources SERVER_DEST_RESOURCE_FILES "${SERVER_RESOURCE_FILES}") endif() # Server landingpage webapp @@ -43,6 +44,32 @@ if (WITH_SERVER_LANDINGPAGE_WEBAPP) add_custom_target (server_landingpage ALL DEPENDS ${LANDINGPAGE_OUTPUT_PATH}/landingpage.stamp) + # add_custom_command does not support OUTPUT_QUIET, so we need to make a cmake subcommand first: + file(WRITE ${CMAKE_BINARY_DIR}/yarn_commands.cmake + "execute_process( + COMMAND ${YARN} install --frozen-lockfile + WORKING_DIRECTORY ${LANDINGPAGE_OUTPUT_PATH} + OUTPUT_QUIET + ERROR_FILE ${CMAKE_BINARY_DIR}/yarn_error.log + RESULT_VARIABLE YARN_RESULT + ) + if(NOT YARN_RESULT EQUAL 0) + file(READ ${CMAKE_BINARY_DIR}/yarn_error.log ERROR_CONTENTS) + message(FATAL_ERROR \${ERROR_CONTENTS}) + endif() + + execute_process( + COMMAND ${YARN} build + WORKING_DIRECTORY ${LANDINGPAGE_OUTPUT_PATH} + OUTPUT_QUIET + ERROR_FILE ${CMAKE_BINARY_DIR}/yarn_error.log + RESULT_VARIABLE YARN_RESULT + ) + if(NOT YARN_RESULT EQUAL 0) + file(READ ${CMAKE_BINARY_DIR}/yarn_error.log ERROR_CONTENTS) + message(FATAL_ERROR \${ERROR_CONTENTS}) + endif() + ") add_custom_command( POST_BUILD OUTPUT ${LANDINGPAGE_OUTPUT_PATH}/landingpage.stamp @@ -51,7 +78,7 @@ if (WITH_SERVER_LANDINGPAGE_WEBAPP) ${CMAKE_CURRENT_SOURCE_DIR}/server/src/landingpage/ ${LANDINGPAGE_OUTPUT_PATH}/ COMMAND ${CMAKE_COMMAND} -E touch ${LANDINGPAGE_OUTPUT_PATH}/landingpage.stamp - COMMAND cd ${LANDINGPAGE_OUTPUT_PATH} && ${YARN} install --frozen-lockfile && ${YARN} build + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/yarn_commands.cmake ) endif() @@ -77,4 +104,5 @@ list(APPEND DEST_RESOURCE_FILES "${CMAKE_CURRENT_BINARY_DIR}/srs.db") install(FILES ${SRSDB} DESTINATION ${QGIS_DATA_DIR}/resources RENAME srs.db) +list(APPEND DEST_RESOURCE_FILES ${SERVER_DEST_RESOURCE_FILES}) add_custom_target(resources ALL DEPENDS ${DEST_RESOURCE_FILES}) diff --git a/resources/server/src/landingpage/yarn.lock b/resources/server/src/landingpage/yarn.lock index e3576685737..3fd583265b8 100644 --- a/resources/server/src/landingpage/yarn.lock +++ b/resources/server/src/landingpage/yarn.lock @@ -5184,9 +5184,9 @@ http-parser-js@>=0.5.1: integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== http-proxy-middleware@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" - integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + version "2.0.7" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.7.tgz#915f236d92ae98ef48278a95dedf17e991936ec6" + integrity sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA== dependencies: "@types/http-proxy" "^1.17.8" http-proxy "^1.18.1" diff --git a/src/3d/chunks/qgschunkedentity.cpp b/src/3d/chunks/qgschunkedentity.cpp index 13d726823f2..27c979f5767 100644 --- a/src/3d/chunks/qgschunkedentity.cpp +++ b/src/3d/chunks/qgschunkedentity.cpp @@ -31,16 +31,16 @@ ///@cond PRIVATE -static float screenSpaceError( QgsChunkNode *node, const QgsChunkedEntity::SceneContext &sceneContext ) +static float screenSpaceError( const QgsAABB &nodeBbox, float nodeError, const QgsChunkedEntity::SceneContext &sceneContext ) { - if ( node->error() <= 0 ) //it happens for meshes + if ( nodeError <= 0 ) //it happens for meshes return 0; - float dist = node->bbox().distanceFromPoint( sceneContext.cameraPos ); + float dist = nodeBbox.distanceFromPoint( sceneContext.cameraPos ); // TODO: what to do when distance == 0 ? - float sse = Qgs3DUtils::screenSpaceError( node->error(), dist, sceneContext.screenSizePx, sceneContext.cameraFov ); + float sse = Qgs3DUtils::screenSpaceError( nodeError, dist, sceneContext.screenSizePx, sceneContext.cameraFov ); return sse; } @@ -194,7 +194,7 @@ void QgsChunkedEntity::handleSceneUpdate( const SceneContext &sceneContext ) { QList bboxes; for ( QgsChunkNode *n : std::as_const( mActiveNodes ) ) - bboxes << n->bbox(); + bboxes << Qgs3DUtils::mapToWorldExtent( n->box3D(), mMapSettings->origin() ); mBboxesEntity->setBoxes( bboxes ); } @@ -282,7 +282,7 @@ QgsRange QgsChunkedEntity::getNearFarPlaneRange( const QMatrix4x4 &viewMa { // project each corner of bbox to camera coordinates // and determine closest and farthest point. - QgsAABB bbox = node->bbox(); + QgsAABB bbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() ); float bboxfnear; float bboxffar; Qgs3DUtils::computeBoundingBoxNearFarPlanes( bbox, viewMatrix, bboxfnear, bboxffar ); @@ -344,7 +344,8 @@ void QgsChunkedEntity::pruneLoaderQueue( const SceneContext &sceneContext ) while ( e ) { Q_ASSERT( e->chunk->state() == QgsChunkNode::QueuedForLoad || e->chunk->state() == QgsChunkNode::QueuedForUpdate ); - if ( Qgs3DUtils::isCullable( e->chunk->bbox(), sceneContext.viewProjectionMatrix ) ) + const QgsAABB bbox = Qgs3DUtils::mapToWorldExtent( e->chunk->box3D(), mMapSettings->origin() ); + if ( Qgs3DUtils::isCullable( bbox, sceneContext.viewProjectionMatrix ) ) { toRemoveFromLoaderQueue.append( e->chunk ); } @@ -417,14 +418,16 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont }; int renderedCount = 0; std::priority_queue, decltype( cmp_funct )> pq( cmp_funct ); - pq.push( std::make_pair( root, screenSpaceError( root, sceneContext ) ) ); + const QgsAABB rootBbox = Qgs3DUtils::mapToWorldExtent( root->box3D(), mMapSettings->origin() ); + pq.push( std::make_pair( root, screenSpaceError( rootBbox, root->error(), sceneContext ) ) ); while ( !pq.empty() && renderedCount <= mPrimitivesBudget ) { slotItem s = pq.top(); pq.pop(); QgsChunkNode *node = s.first; - if ( Qgs3DUtils::isCullable( node->bbox(), sceneContext.viewProjectionMatrix ) ) + const QgsAABB bbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() ); + if ( Qgs3DUtils::isCullable( bbox, sceneContext.viewProjectionMatrix ) ) { ++mFrustumCulled; continue; @@ -453,7 +456,7 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont // make sure all nodes leading to children are always loaded // so that zooming out does not create issues - double dist = node->bbox().center().distanceToPoint( sceneContext.cameraPos ); + double dist = bbox.center().distanceToPoint( sceneContext.cameraPos ); residencyRequests.push_back( ResidencyRequest( node, dist, node->level() ) ); if ( !node->entity() && node->hasData() ) @@ -470,7 +473,7 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont // or not, it's the best we'll ever get... becomesActive = true; } - else if ( mTau > 0 && screenSpaceError( node, sceneContext ) <= mTau && node->hasData() ) + else if ( mTau > 0 && screenSpaceError( bbox, node->error(), sceneContext ) <= mTau && node->hasData() ) { // acceptable error for the current chunk - let's render it becomesActive = true; @@ -492,18 +495,19 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont QgsChunkNode *const *children = node->children(); for ( int i = 0; i < node->childCount(); ++i ) { + const QgsAABB childBbox = Qgs3DUtils::mapToWorldExtent( children[i]->box3D(), mMapSettings->origin() ); if ( children[i]->entity() || !children[i]->hasData() ) { // chunk is resident - let's visit it recursively - pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) ); + pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) ); } else { // chunk is not yet resident - let's try to load it - if ( Qgs3DUtils::isCullable( children[i]->bbox(), sceneContext.viewProjectionMatrix ) ) + if ( Qgs3DUtils::isCullable( childBbox, sceneContext.viewProjectionMatrix ) ) continue; - double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos ); + double dist = childBbox.center().distanceToPoint( sceneContext.cameraPos ); residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) ); } } @@ -517,7 +521,10 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont { QgsChunkNode *const *children = node->children(); for ( int i = 0; i < node->childCount(); ++i ) - pq.push( std::make_pair( children[i], screenSpaceError( children[i], sceneContext ) ) ); + { + const QgsAABB childBbox = Qgs3DUtils::mapToWorldExtent( children[i]->box3D(), mMapSettings->origin() ); + pq.push( std::make_pair( children[i], screenSpaceError( childBbox, children[i]->error(), sceneContext ) ) ); + } } else { @@ -526,7 +533,8 @@ void QgsChunkedEntity::update( QgsChunkNode *root, const SceneContext &sceneCont QgsChunkNode *const *children = node->children(); for ( int i = 0; i < node->childCount(); ++i ) { - double dist = children[i]->bbox().center().distanceToPoint( sceneContext.cameraPos ); + const QgsAABB childBbox = Qgs3DUtils::mapToWorldExtent( children[i]->box3D(), mMapSettings->origin() ); + double dist = childBbox.center().distanceToPoint( sceneContext.cameraPos ); residencyRequests.push_back( ResidencyRequest( children[i], dist, children[i]->level() ) ); } } diff --git a/src/3d/chunks/qgschunkloader.cpp b/src/3d/chunks/qgschunkloader.cpp index 59ce928df7d..bfaee18bf2a 100644 --- a/src/3d/chunks/qgschunkloader.cpp +++ b/src/3d/chunks/qgschunkloader.cpp @@ -23,17 +23,17 @@ QgsQuadtreeChunkLoaderFactory::QgsQuadtreeChunkLoaderFactory() = default; QgsQuadtreeChunkLoaderFactory::~QgsQuadtreeChunkLoaderFactory() = default; -void QgsQuadtreeChunkLoaderFactory::setupQuadtree( const QgsAABB &rootBbox, float rootError, int maxLevel, const QgsAABB &clippingBbox ) +void QgsQuadtreeChunkLoaderFactory::setupQuadtree( const QgsBox3D &rootBox3D, float rootError, int maxLevel, const QgsBox3D &clippingBox3D ) { - mRootBbox = rootBbox; + mRootBox3D = rootBox3D; mRootError = rootError; mMaxLevel = maxLevel; - mClippingBbox = clippingBbox; + mClippingBox3D = clippingBox3D; } QgsChunkNode *QgsQuadtreeChunkLoaderFactory::createRootNode() const { - return new QgsChunkNode( QgsChunkNodeId( 0, 0, 0 ), mRootBbox, mRootError ); + return new QgsChunkNode( QgsChunkNodeId( 0, 0, 0 ), mRootBox3D, mRootError ); } QVector QgsQuadtreeChunkLoaderFactory::createChildren( QgsChunkNode *node ) const @@ -45,25 +45,24 @@ QVector QgsQuadtreeChunkLoaderFactory::createChildren( QgsChunkN const QgsChunkNodeId nodeId = node->tileId(); const float childError = node->error() / 2; - const QgsAABB bbox = node->bbox(); - float xc = bbox.xCenter(), zc = bbox.zCenter(); + const QgsBox3D box3D = node->box3D(); + QgsVector3D center = box3D.center(); for ( int i = 0; i < 4; ++i ) { int dx = i & 1, dy = !!( i & 2 ); - const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + ( dy ? 0 : 1 ) ); // TODO: inverse dy? - // the Y and Z coordinates below are intentionally flipped, because - // in chunk node IDs the X,Y axes define horizontal plane, - // while in our 3D scene the X,Z axes define the horizontal plane - const float chXMin = dx ? xc : bbox.xMin; - const float chXMax = dx ? bbox.xMax : xc; - const float chZMin = dy ? zc : bbox.zMin; - const float chZMax = dy ? bbox.zMax : zc; - const float chYMin = bbox.yMin; - const float chYMax = bbox.yMax; - const QgsAABB childBbox = QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ); - if ( mClippingBbox.isEmpty() || childBbox.intersects( mClippingBbox ) ) - children << new QgsChunkNode( childId, childBbox, childError, node ); + const QgsChunkNodeId childId( nodeId.d + 1, nodeId.x * 2 + dx, nodeId.y * 2 + dy ); + + const double chXMin = dx ? center.x() : box3D.xMinimum(); + const double chXMax = dx ? box3D.xMaximum() : center.x(); + const double chYMin = dy ? center.y() : box3D.yMinimum(); + const double chYMax = dy ? box3D.yMaximum() : center.y(); + const double chZMin = box3D.zMinimum(); + const double chZMax = box3D.zMaximum(); + const QgsBox3D childBox3D( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ); + + if ( mClippingBox3D.isEmpty() || childBox3D.intersects( mClippingBox3D ) ) + children << new QgsChunkNode( childId, childBox3D, childError, node ); } return children; } diff --git a/src/3d/chunks/qgschunkloader.h b/src/3d/chunks/qgschunkloader.h index d726f5c2a5d..a716537ff77 100644 --- a/src/3d/chunks/qgschunkloader.h +++ b/src/3d/chunks/qgschunkloader.h @@ -27,7 +27,9 @@ // version without notice, or even be removed. // +#include "qgis_3d.h" #include "qgschunkqueuejob.h" +#include "qgsbox3d.h" #define SIP_NO_FILE @@ -109,8 +111,6 @@ class QgsChunkLoaderFactory : public QObject }; -#include "qgsaabb.h" - /** * \ingroup 3d * \brief Base class for factories where the hierarchy is a quadtree where all leaves @@ -126,14 +126,14 @@ class _3D_EXPORT QgsQuadtreeChunkLoaderFactory : public QgsChunkLoaderFactory virtual ~QgsQuadtreeChunkLoaderFactory(); //! Initializes the root node setup (bounding box and error) and tree depth - void setupQuadtree( const QgsAABB &rootBbox, float rootError, int maxLevel, const QgsAABB &clippingBbox = QgsAABB() ); + void setupQuadtree( const QgsBox3D &rootBox3D, float rootError, int maxLevel, const QgsBox3D &clippingBox3D = QgsBox3D() ); virtual QgsChunkNode *createRootNode() const override; virtual QVector createChildren( QgsChunkNode *node ) const override; protected: - QgsAABB mRootBbox; - QgsAABB mClippingBbox; + QgsBox3D mRootBox3D; + QgsBox3D mClippingBox3D; float mRootError = 0; //! maximum allowed depth of quad tree int mMaxLevel = 0; diff --git a/src/3d/chunks/qgschunknode.cpp b/src/3d/chunks/qgschunknode.cpp index 54b53c406b9..d6df1fd7251 100644 --- a/src/3d/chunks/qgschunknode.cpp +++ b/src/3d/chunks/qgschunknode.cpp @@ -22,8 +22,8 @@ ///@cond PRIVATE -QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent ) - : mBbox( bbox ) +QgsChunkNode::QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsBox3D &box3D, float error, QgsChunkNode *parent ) + : mBox3D( box3D ) , mError( error ) , mNodeId( nodeId ) , mParent( parent ) @@ -238,9 +238,9 @@ void QgsChunkNode::setUpdated() mState = QgsChunkNode::Loaded; } -void QgsChunkNode::setExactBbox( const QgsAABB &box ) +void QgsChunkNode::setExactBox3D( const QgsBox3D &box3D ) { - mBbox = box; + mBox3D = box3D; // TODO: propagate better estimate to children? } @@ -251,40 +251,40 @@ void QgsChunkNode::updateParentBoundingBoxesRecursively() const while ( currentNode ) { QgsChunkNode *const *currentNodeChildren = currentNode->children(); - float xMin = std::numeric_limits< float >::max(); - float xMax = -std::numeric_limits< float >::max(); - float yMin = std::numeric_limits< float >::max(); - float yMax = -std::numeric_limits< float >::max(); - float zMin = std::numeric_limits< float >::max(); - float zMax = -std::numeric_limits< float >::max(); + double xMin = std::numeric_limits< double >::max(); + double xMax = -std::numeric_limits< double >::max(); + double yMin = std::numeric_limits< double >::max(); + double yMax = -std::numeric_limits< double >::max(); + double zMin = std::numeric_limits< double >::max(); + double zMax = -std::numeric_limits< double >::max(); for ( int i = 0; i < currentNode->childCount(); ++i ) { - const QgsAABB childBBox = currentNodeChildren[i]->bbox(); + const QgsBox3D childBox3D = currentNodeChildren[i]->box3D(); // Nodes without data have an empty bbox and should be skipped - if ( childBBox.isEmpty() ) + if ( childBox3D.isEmpty() ) continue; - if ( childBBox.xMin < xMin ) - xMin = childBBox.xMin; - if ( childBBox.yMin < yMin ) - yMin = childBBox.yMin; - if ( childBBox.zMin < zMin ) - zMin = childBBox.zMin; - if ( childBBox.xMax > xMax ) - xMax = childBBox.xMax; - if ( childBBox.yMax > yMax ) - yMax = childBBox.yMax; - if ( childBBox.zMax > zMax ) - zMax = childBBox.zMax; + if ( childBox3D.xMinimum() < xMin ) + xMin = childBox3D.xMinimum(); + if ( childBox3D.yMinimum() < yMin ) + yMin = childBox3D.yMinimum(); + if ( childBox3D.zMinimum() < zMin ) + zMin = childBox3D.zMinimum(); + if ( childBox3D.xMaximum() > xMax ) + xMax = childBox3D.xMaximum(); + if ( childBox3D.yMaximum() > yMax ) + yMax = childBox3D.yMaximum(); + if ( childBox3D.zMaximum() > zMax ) + zMax = childBox3D.zMaximum(); } - // QgsAABB is normalized in its constructor, so that min values are always smaller than max. + // QgsBox3D is normalized in its constructor, so that min values are always smaller than max. // If all child bboxes were empty, we can end up with min > max, so let's have an empty bbox instead. - const QgsAABB currentNodeBbox = xMin > xMax || yMin > yMax || zMin > zMax ? QgsAABB() : QgsAABB( xMin, yMin, zMin, xMax, yMax, zMax ); + const QgsBox3D currentNodeBox3D = xMin > xMax || yMin > yMax || zMin > zMax ? QgsBox3D() : QgsBox3D( xMin, yMin, zMin, xMax, yMax, zMax ); - currentNode->setExactBbox( currentNodeBbox ); + currentNode->setExactBox3D( currentNodeBox3D ); currentNode = currentNode->parent(); } } diff --git a/src/3d/chunks/qgschunknode.h b/src/3d/chunks/qgschunknode.h index a32aa3372e5..70a4b210b29 100644 --- a/src/3d/chunks/qgschunknode.h +++ b/src/3d/chunks/qgschunknode.h @@ -28,6 +28,7 @@ // #include "qgsaabb.h" +#include "qgsbox3d.h" #include "qgis.h" #include @@ -113,7 +114,9 @@ struct QgsChunkNodeId * This is currently used for rendering of terrain, but it is not limited to it and may be used for * other data as well. * - * The data structure is essentially a quadtree: each node may have four child nodes. Nodes can exist + * The data structure is a tree: each node may have several child nodes, all child nodes should + * have their bounding box within the bounding box of their parent. Typically this is a quadtree, + * an octree, but it may be also more general structure (with variable number of children). Nodes can exist * in several states (e.g. skeleton or loaded state) and they keep being loaded and unloaded as necessary * by the 3D rendering. * @@ -123,7 +126,7 @@ class QgsChunkNode public: //! constructs a skeleton chunk - QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsAABB &bbox, float error, QgsChunkNode *parent = nullptr ); + QgsChunkNode( const QgsChunkNodeId &nodeId, const QgsBox3D &box3D, float error, QgsChunkNode *parent = nullptr ); ~QgsChunkNode(); @@ -155,8 +158,8 @@ class QgsChunkNode Updating, //!< Data are being updated right now }; - //! Returns 3D bounding box of the chunk - QgsAABB bbox() const { return mBbox; } + //! Returns 3D bounding box (in map coordinates) of the chunk + QgsBox3D box3D() const { return mBox3D; } //! Returns measure geometric/texture error of the chunk (in world coordinates) float error() const { return mError; } //! Returns chunk tile coordinates of the tiling scheme @@ -238,8 +241,8 @@ class QgsChunkNode //! mark node that it finished updating - back to loaded node void setUpdated(); - //! called when bounding box - void setExactBbox( const QgsAABB &box ); + //! called when the true bounding box is known so that we can use tighter bounding box + void setExactBox3D( const QgsBox3D &box3D ); /** * Triggers a recursive update of the node's parent's bounding boxes to ensure @@ -256,7 +259,7 @@ class QgsChunkNode bool hasData() const { return mHasData; } private: - QgsAABB mBbox; //!< Bounding box in world coordinates + QgsBox3D mBox3D; //!< Bounding box in map coordinates float mError; //!< Error of the node in world coordinates (negative error means that chunk at this level has no data, but there may be children that do) QgsChunkNodeId mNodeId; //!< Chunk coordinates (for use with a tiling scheme) diff --git a/src/3d/qgs3dmapscene.cpp b/src/3d/qgs3dmapscene.cpp index f23925054b7..9249f25e84c 100644 --- a/src/3d/qgs3dmapscene.cpp +++ b/src/3d/qgs3dmapscene.cpp @@ -471,10 +471,10 @@ void Qgs3DMapScene::createTerrainDeferred() { double tile0width = mMap.terrainGenerator()->rootChunkExtent().width(); int maxZoomLevel = Qgs3DUtils::maxZoomLevel( tile0width, mMap.mapTileResolution(), mMap.maxTerrainGroundError() ); - QgsAABB rootBbox = mMap.terrainGenerator()->rootChunkBbox( mMap ); + const QgsBox3D rootBox3D = mMap.terrainGenerator()->rootChunkBox3D( mMap ); float rootError = mMap.terrainGenerator()->rootChunkError( mMap ); - const QgsAABB clippingBbox = Qgs3DUtils::mapToWorldExtent( mMap.extent(), rootBbox.zMin, rootBbox.zMax, mMap.origin() ); - mMap.terrainGenerator()->setupQuadtree( rootBbox, rootError, maxZoomLevel, clippingBbox ); + const QgsBox3D clippingBox3D( mMap.extent(), rootBox3D.zMinimum(), rootBox3D.zMaximum() ); + mMap.terrainGenerator()->setupQuadtree( rootBox3D, rootError, maxZoomLevel, clippingBox3D ); mTerrain = new QgsTerrainEntity( &mMap ); mTerrain->setParent( this ); @@ -1069,9 +1069,9 @@ QgsDoubleRange Qgs3DMapScene::elevationRange() const double yMax = std::numeric_limits< double >::lowest(); if ( mMap.terrainRenderingEnabled() && mTerrain ) { - const QgsAABB bbox = mTerrain->rootNode()->bbox(); - yMin = std::min( yMin, static_cast< double >( bbox.yMin ) ); - yMax = std::max( yMax, static_cast< double >( bbox.yMax ) ); + const QgsBox3D box3D = mTerrain->rootNode()->box3D(); + yMin = std::min( yMin, box3D.zMinimum() ); + yMax = std::max( yMax, box3D.zMaximum() ); } for ( auto it = mLayerEntities.constBegin(); it != mLayerEntities.constEnd(); it++ ) diff --git a/src/3d/qgs3dmapscene.h b/src/3d/qgs3dmapscene.h index f5be9549c92..3a880f59d87 100644 --- a/src/3d/qgs3dmapscene.h +++ b/src/3d/qgs3dmapscene.h @@ -220,12 +220,12 @@ class _3D_EXPORT Qgs3DMapScene : public QObject * A plane equation contains 4 elements. * A simple way to define a clip plane equation is to define a normalized normal to * the plane and its distance from the origin of the scene. - * In that case, the first 3 elements are the coordinates of the normal of the plane as (X, Y, Z). + * In that case, the first 3 elements are the coordinates of the normal of the plane as ``(X, Y, Z)``. * They need to be normalized. * The last element is the distance of the plane from the origin of the scene. - * In mathematical terms, a 3d plane can be defined with the equation ax+by+cz+d=0 - * The normal is (a, b, c) with |a, b, c| = 1 - * The distance is -d. + * In mathematical terms, a 3d plane can be defined with the equation ``ax+by+cz+d=0`` + * The normal is ``(a, b, c)`` with ``|a, b, c| = 1`` + * The distance is ``-d``. * * By default, OpenGL supports up to 8 additional clipping planes. If \a clipPlaneEquations * contains more than 8 planes, only the first 8 ones will be used. diff --git a/src/3d/qgs3dutils.cpp b/src/3d/qgs3dutils.cpp index 291f1868d67..bbad2c8e165 100644 --- a/src/3d/qgs3dutils.cpp +++ b/src/3d/qgs3dutils.cpp @@ -488,7 +488,7 @@ QMatrix4x4 Qgs3DUtils::stringToMatrix4x4( const QString &str ) return m; } -void Qgs3DUtils::extractPointPositions( const QgsFeature &f, const Qgs3DRenderContext &context, Qgis::AltitudeClamping altClamp, QVector &positions ) +void Qgs3DUtils::extractPointPositions( const QgsFeature &f, const Qgs3DRenderContext &context, const QgsVector3D &chunkOrigin, Qgis::AltitudeClamping altClamp, QVector &positions ) { const QgsAbstractGeometry *g = f.geometry().constGet(); for ( auto it = g->vertices_begin(); it != g->vertices_end(); ++it ) @@ -513,7 +513,10 @@ void Qgs3DUtils::extractPointPositions( const QgsFeature &f, const Qgs3DRenderCo h = terrainZ + geomZ; break; } - positions.append( QVector3D( pt.x() - context.origin().x(), h, -( pt.y() - context.origin().y() ) ) ); + positions.append( QVector3D( + static_cast( pt.x() - chunkOrigin.x() ), + static_cast( pt.y() - chunkOrigin.y() ), + h ) ); QgsDebugMsgLevel( QStringLiteral( "%1 %2 %3" ).arg( positions.last().x() ).arg( positions.last().y() ).arg( positions.last().z() ), 2 ); } } @@ -630,6 +633,21 @@ QgsAABB Qgs3DUtils::mapToWorldExtent( const QgsRectangle &extent, double zMin, d return rootBbox; } +QgsAABB Qgs3DUtils::mapToWorldExtent( const QgsBox3D &box3D, const QgsVector3D &mapOrigin ) +{ + const QgsVector3D extentMin3D( box3D.xMinimum(), box3D.yMinimum(), box3D.zMinimum() ); + const QgsVector3D extentMax3D( box3D.xMaximum(), box3D.yMaximum(), box3D.zMaximum() ); + const QgsVector3D worldExtentMin3D = mapToWorldCoordinates( extentMin3D, mapOrigin ); + const QgsVector3D worldExtentMax3D = mapToWorldCoordinates( extentMax3D, mapOrigin ); + // casting to float should be ok, assuming that the map origin is not too far from the box + return QgsAABB( static_cast( worldExtentMin3D.x() ), + static_cast( worldExtentMin3D.y() ), + static_cast( worldExtentMin3D.z() ), + static_cast( worldExtentMax3D.x() ), + static_cast( worldExtentMax3D.y() ), + static_cast( worldExtentMax3D.z() ) ); +} + QgsRectangle Qgs3DUtils::worldToMapExtent( const QgsAABB &bbox, const QgsVector3D &mapOrigin ) { const QgsVector3D worldExtentMin3D = Qgs3DUtils::worldToMapCoordinates( QgsVector3D( bbox.xMin, bbox.yMin, bbox.zMin ), mapOrigin ); diff --git a/src/3d/qgs3dutils.h b/src/3d/qgs3dutils.h index 388a219fae6..ae0f8cf04aa 100644 --- a/src/3d/qgs3dutils.h +++ b/src/3d/qgs3dutils.h @@ -143,7 +143,7 @@ class _3D_EXPORT Qgs3DUtils static QMatrix4x4 stringToMatrix4x4( const QString &str ); //! Calculates (x,y,z) positions of (multi)point from the given feature - static void extractPointPositions( const QgsFeature &f, const Qgs3DRenderContext &context, Qgis::AltitudeClamping altClamp, QVector &positions ); + static void extractPointPositions( const QgsFeature &f, const Qgs3DRenderContext &context, const QgsVector3D &chunkOrigin, Qgis::AltitudeClamping altClamp, QVector &positions ); /** * Returns TRUE if bbox is completely outside the current viewing volume. @@ -174,6 +174,12 @@ class _3D_EXPORT Qgs3DUtils */ static QgsAABB mapToWorldExtent( const QgsRectangle &extent, double zMin, double zMax, const QgsVector3D &mapOrigin ); + /** + * Converts 3D box in map coordinates to AABB in world coordinates. + * \since QGIS 3.42 + */ + static QgsAABB mapToWorldExtent( const QgsBox3D &box3D, const QgsVector3D &mapOrigin ); + /** * Converts axis aligned bounding box in 3D world coordinates to extent in map coordinates * \since QGIS 3.12 diff --git a/src/3d/qgsfeature3dhandler_p.cpp b/src/3d/qgsfeature3dhandler_p.cpp index 8164033e7d8..857d4f71675 100644 --- a/src/3d/qgsfeature3dhandler_p.cpp +++ b/src/3d/qgsfeature3dhandler_p.cpp @@ -28,10 +28,10 @@ void QgsFeature3DHandler::updateZRangeFromPositions( const QVector &p { for ( const QVector3D &pos : positions ) { - if ( pos.y() < mZMin ) - mZMin = pos.y(); - if ( pos.y() > mZMax ) - mZMax = pos.y(); + if ( pos.z() < mZMin ) + mZMin = pos.z(); + if ( pos.z() > mZMax ) + mZMax = pos.z(); } } diff --git a/src/3d/qgsfeature3dhandler_p.h b/src/3d/qgsfeature3dhandler_p.h index a6e3366f8cb..a1f89d6a4d1 100644 --- a/src/3d/qgsfeature3dhandler_p.h +++ b/src/3d/qgsfeature3dhandler_p.h @@ -49,7 +49,7 @@ class QgsFeature3DHandler * Called before feature iteration starts to initialize, get required attributes. * \returns TRUE on success (on FALSE the handler failed to initialize and processFeature() / finalize() should not be called */ - virtual bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) = 0; + virtual bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) = 0; /** * Called for every feature to extract information out of it into some diff --git a/src/3d/qgspointcloudlayerchunkloader_p.cpp b/src/3d/qgspointcloudlayerchunkloader_p.cpp index 71e10777ff5..80dbbca5c98 100644 --- a/src/3d/qgspointcloudlayerchunkloader_p.cpp +++ b/src/3d/qgspointcloudlayerchunkloader_p.cpp @@ -81,8 +81,8 @@ QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointClou mFutureWatcher = new QFutureWatcher( this ); connect( mFutureWatcher, &QFutureWatcher::finished, this, &QgsChunkQueueJob::finished ); - const QgsAABB bbox = node->bbox(); - const QFuture future = QtConcurrent::run( [pc, pcNode, bbox, this] + const QgsBox3D box3D = node->box3D(); + const QFuture future = QtConcurrent::run( [pc, pcNode, box3D, this] { const QgsEventTracing::ScopedEvent e( QStringLiteral( "3D" ), QStringLiteral( "PC chunk load" ) ); @@ -101,7 +101,7 @@ QgsPointCloudLayerChunkLoader::QgsPointCloudLayerChunkLoader( const QgsPointClou } if ( mContext.symbol()->renderAsTriangles() ) - mHandler->triangulate( pc, pcNode, mContext, bbox ); + mHandler->triangulate( pc, pcNode, mContext, box3D ); } ); // emit finished() as soon as the handler is populated with features @@ -177,13 +177,38 @@ int QgsPointCloudLayerChunkLoaderFactory::primitivesCount( QgsChunkNode *node ) return mPointCloudIndex->nodePointCount( n ); } -QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DRenderContext &context, const QgsCoordinateTransform &coordinateTransform, double zValueOffset ); + +QgsBox3D nodeBoundsToBox3D( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const QgsCoordinateTransform &coordinateTransform, double zValueOffset, double zValueScale ) +{ + QgsVector3D extentMin3D( static_cast( nodeBounds.xMin() ) * scale.x() + offset.x(), + static_cast( nodeBounds.yMin() ) * scale.y() + offset.y(), + ( static_cast( nodeBounds.zMin() ) * scale.z() + offset.z() ) * zValueScale + zValueOffset ); + QgsVector3D extentMax3D( static_cast( nodeBounds.xMax() ) * scale.x() + offset.x(), + static_cast( nodeBounds.yMax() ) * scale.y() + offset.y(), + ( static_cast( nodeBounds.zMax() ) * scale.z() + offset.z() ) * zValueScale + zValueOffset ); + QgsCoordinateTransform extentTransform = coordinateTransform; + extentTransform.setBallparkTransformsAreAppropriate( true ); + try + { + extentMin3D = extentTransform.transform( extentMin3D ); + extentMax3D = extentTransform.transform( extentMax3D ); + } + catch ( QgsCsException & ) + { + QgsDebugError( QStringLiteral( "Error transforming node bounds coordinate" ) ); + } + return QgsBox3D( extentMin3D.x(), extentMin3D.y(), extentMin3D.z(), + extentMax3D.x(), extentMax3D.y(), extentMax3D.z() ); +} + QgsChunkNode *QgsPointCloudLayerChunkLoaderFactory::createRootNode() const { - const QgsAABB bbox = nodeBoundsToAABB( mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ), mPointCloudIndex->offset(), mPointCloudIndex->scale(), mRenderContext, mCoordinateTransform, mZValueOffset ); + const QgsPointCloudDataBounds rootNodeBounds = mPointCloudIndex->nodeBounds( IndexedPointCloudNode( 0, 0, 0, 0 ) ); + QgsBox3D rootNodeBox3D = nodeBoundsToBox3D( rootNodeBounds, mPointCloudIndex->offset(), mPointCloudIndex->scale(), mCoordinateTransform, mZValueOffset, mZValueScale ); + const float error = mPointCloudIndex->nodeError( IndexedPointCloudNode( 0, 0, 0, 0 ) ); - QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), bbox, error ); + QgsChunkNode *node = new QgsChunkNode( QgsChunkNodeId( 0, 0, 0, 0 ), rootNodeBox3D, error ); node->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive ); return node; } @@ -192,9 +217,7 @@ QVector QgsPointCloudLayerChunkLoaderFactory::createChildren( Qg { QVector children; const QgsChunkNodeId nodeId = node->tileId(); - const QgsAABB bbox = node->bbox(); const float childError = node->error() / 2; - float xc = bbox.xCenter(), yc = bbox.yCenter(), zc = bbox.zCenter(); for ( int i = 0; i < 8; ++i ) { @@ -207,17 +230,10 @@ QVector QgsPointCloudLayerChunkLoaderFactory::createChildren( Qg !mPointCloudIndex->nodeMapExtent( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ).intersects( mExtent ) ) continue; - // the Y and Z coordinates below are intentionally flipped, because - // in chunk node IDs the X,Y axes define horizontal plane, - // while in our 3D scene the X,Z axes define the horizontal plane - const float chXMin = dx ? xc : bbox.xMin; - const float chXMax = dx ? bbox.xMax : xc; - // Z axis: values are increasing to the south - const float chZMin = !dy ? zc : bbox.zMin; - const float chZMax = !dy ? bbox.zMax : zc; - const float chYMin = dz ? yc : bbox.yMin; - const float chYMax = dz ? bbox.yMax : yc; - QgsChunkNode *child = new QgsChunkNode( childId, QgsAABB( chXMin, chYMin, chZMin, chXMax, chYMax, chZMax ), childError, node ); + const QgsPointCloudDataBounds childBounds = mPointCloudIndex->nodeBounds( IndexedPointCloudNode( childId.d, childId.x, childId.y, childId.z ) ); + QgsBox3D childBox3D = nodeBoundsToBox3D( childBounds, mPointCloudIndex->offset(), mPointCloudIndex->scale(), mCoordinateTransform, mZValueOffset, mZValueScale ); + + QgsChunkNode *child = new QgsChunkNode( childId, childBox3D, childError, node ); child->setRefinementProcess( mSymbol->renderAsTriangles() ? Qgis::TileRefinementProcess::Replacement : Qgis::TileRefinementProcess::Additive ); children << child; } @@ -227,29 +243,6 @@ QVector QgsPointCloudLayerChunkLoaderFactory::createChildren( Qg /////////////// -QgsAABB nodeBoundsToAABB( QgsPointCloudDataBounds nodeBounds, QgsVector3D offset, QgsVector3D scale, const Qgs3DRenderContext &context, const QgsCoordinateTransform &coordinateTransform, double zValueOffset ) -{ - QgsVector3D extentMin3D( nodeBounds.xMin() * scale.x() + offset.x(), nodeBounds.yMin() * scale.y() + offset.y(), nodeBounds.zMin() * scale.z() + offset.z() + zValueOffset ); - QgsVector3D extentMax3D( nodeBounds.xMax() * scale.x() + offset.x(), nodeBounds.yMax() * scale.y() + offset.y(), nodeBounds.zMax() * scale.z() + offset.z() + zValueOffset ); - QgsCoordinateTransform extentTransform = coordinateTransform; - extentTransform.setBallparkTransformsAreAppropriate( true ); - try - { - extentMin3D = extentTransform.transform( extentMin3D ); - extentMax3D = extentTransform.transform( extentMax3D ); - } - catch ( QgsCsException & ) - { - QgsDebugError( QStringLiteral( "Error transforming node bounds coordinate" ) ); - } - const QgsVector3D worldExtentMin3D = Qgs3DUtils::mapToWorldCoordinates( extentMin3D, context.origin() ); - const QgsVector3D worldExtentMax3D = Qgs3DUtils::mapToWorldCoordinates( extentMax3D, context.origin() ); - QgsAABB rootBbox( worldExtentMin3D.x(), worldExtentMin3D.y(), worldExtentMin3D.z(), - worldExtentMax3D.x(), worldExtentMax3D.y(), worldExtentMax3D.z() ); - return rootBbox; -} - - QgsPointCloudLayerChunkedEntity::QgsPointCloudLayerChunkedEntity( Qgs3DMapSettings *map, QgsPointCloudIndex *pc, const QgsCoordinateTransform &coordinateTransform, QgsPointCloud3DSymbol *symbol, float maximumScreenSpaceError, bool showBoundingBoxes, @@ -313,7 +306,8 @@ QVector QgsPointCloudLayerChunkedEntity::rayIntersec if ( !index->hasNode( n ) ) continue; - if ( !QgsRayCastingUtils::rayBoxIntersection( ray, node->bbox() ) ) + const QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() ); + if ( !QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) ) continue; std::unique_ptr block( index->nodeData( n, request ) ); diff --git a/src/3d/qgsrubberband3d.cpp b/src/3d/qgsrubberband3d.cpp index f00f5c177d4..9c9b9a41361 100644 --- a/src/3d/qgsrubberband3d.cpp +++ b/src/3d/qgsrubberband3d.cpp @@ -80,6 +80,10 @@ QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsWindow3DEngine *engi mLineEntity->addComponent( mLineMaterial ); + Qt3DCore::QTransform *lineTransform = new Qt3DCore::QTransform; + lineTransform->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), -90 ) ); // flip map (x,y,z) to world (x,z,-y) + mLineEntity->addComponent( lineTransform ); + // Rubberband vertex markers mMarkerEntity = new Qt3DCore::QEntity( parentEntity ); mMarkerGeometry = new QgsBillboardGeometry(); @@ -99,6 +103,10 @@ QgsRubberBand3D::QgsRubberBand3D( Qgs3DMapSettings &map, QgsWindow3DEngine *engi mMarkerSymbol = QgsMarkerSymbol::createSimple( props ); updateMarkerMaterial(); mMarkerEntity->addComponent( mMarkerGeometryRenderer ); + + Qt3DCore::QTransform *markerTransform = new Qt3DCore::QTransform; + markerTransform->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), -90 ) ); // flip map (x,y,z) to world (x,z,-y) + mMarkerEntity->addComponent( markerTransform ); } QgsRubberBand3D::~QgsRubberBand3D() @@ -167,7 +175,7 @@ void QgsRubberBand3D::updateGeometry() { QgsLineVertexData lineData; lineData.withAdjacency = true; - lineData.init( Qgis::AltitudeClamping::Absolute, Qgis::AltitudeBinding::Vertex, 0, Qgs3DRenderContext::fromMapSettings( mMapSettings ) ); + lineData.init( Qgis::AltitudeClamping::Absolute, Qgis::AltitudeBinding::Vertex, 0, Qgs3DRenderContext::fromMapSettings( mMapSettings ), mMapSettings->origin() ); lineData.addLineString( mLineString ); mPositionAttribute->buffer()->setData( lineData.createVertexBuffer() ); diff --git a/src/3d/qgsrulebased3drenderer.cpp b/src/3d/qgsrulebased3drenderer.cpp index 7afb11e6f1b..cc0e1e51ed6 100644 --- a/src/3d/qgsrulebased3drenderer.cpp +++ b/src/3d/qgsrulebased3drenderer.cpp @@ -262,12 +262,12 @@ void QgsRuleBased3DRenderer::Rule::createHandlers( QgsVectorLayer *layer, QgsRul } -void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const +void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin, QgsRuleBased3DRenderer::RuleToHandlerMap &handlers ) const { if ( mSymbol ) { QgsFeature3DHandler *handler = handlers[this]; - if ( !handler->prepare( context, attributeNames ) ) + if ( !handler->prepare( context, attributeNames, chunkOrigin ) ) { handlers.remove( this ); delete handler; @@ -283,7 +283,7 @@ void QgsRuleBased3DRenderer::Rule::prepare( const Qgs3DRenderContext &context, Q // call recursively for ( Rule *rule : std::as_const( mChildren ) ) { - rule->prepare( context, attributeNames, handlers ); + rule->prepare( context, attributeNames, chunkOrigin, handlers ); } } diff --git a/src/3d/qgsrulebased3drenderer.h b/src/3d/qgsrulebased3drenderer.h index f1dbc32880a..a6855f307b6 100644 --- a/src/3d/qgsrulebased3drenderer.h +++ b/src/3d/qgsrulebased3drenderer.h @@ -242,7 +242,7 @@ class _3D_EXPORT QgsRuleBased3DRenderer : public QgsAbstractVectorLayer3DRendere * call prepare() on handlers and populate attributeNames * \note not available in Python bindings */ - void prepare( const Qgs3DRenderContext &context, QSet &attributeNames, RuleToHandlerMap &handlers ) const SIP_SKIP; + void prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin, RuleToHandlerMap &handlers ) const SIP_SKIP; /** * register individual features diff --git a/src/3d/qgsrulebasedchunkloader_p.cpp b/src/3d/qgsrulebasedchunkloader_p.cpp index 0fc3985e435..807ec43a4d4 100644 --- a/src/3d/qgsrulebasedchunkloader_p.cpp +++ b/src/3d/qgsrulebasedchunkloader_p.cpp @@ -50,6 +50,12 @@ QgsRuleBasedChunkLoader::QgsRuleBasedChunkLoader( const QgsRuleBasedChunkLoaderF QgsVectorLayer *layer = mFactory->mLayer; + // only a subset of data to be queried + const QgsRectangle rect = node->box3D().toRectangle(); + // origin for coordinates of the chunk - it is kind of arbitrary, but it should be + // picked so that the coordinates are relatively small to avoid numerical precision issues + QgsVector3D chunkOrigin( rect.center().x(), rect.center().y(), 0 ); + QgsExpressionContext exprContext( Qgs3DUtils::globalProjectLayerExpressionContext( layer ) ); exprContext.setFields( layer->fields() ); mContext.setExpressionContext( exprContext ); @@ -63,15 +69,12 @@ QgsRuleBasedChunkLoader::QgsRuleBasedChunkLoader( const QgsRuleBasedChunkLoaderF mRootRule->createHandlers( layer, mHandlers ); QSet attributeNames; - mRootRule->prepare( mContext, attributeNames, mHandlers ); + mRootRule->prepare( mContext, attributeNames, chunkOrigin, mHandlers ); // build the feature request QgsFeatureRequest req; req.setDestinationCrs( mContext.crs(), mContext.transformContext() ); req.setSubsetOfAttributes( attributeNames, layer->fields() ); - - // only a subset of data to be queried - const QgsRectangle rect = Qgs3DUtils::worldToMapExtent( node->bbox(), mContext.origin() ); req.setFilterRect( rect ); // @@ -150,10 +153,10 @@ Qt3DCore::QEntity *QgsRuleBasedChunkLoader::createEntity( Qt3DCore::QEntity *par // fix the vertical range of the node from the estimated vertical range to the true range if ( zMin != std::numeric_limits::max() && zMax != std::numeric_limits::lowest() ) { - QgsAABB box = mNode->bbox(); - box.yMin = zMin; - box.yMax = zMax; - mNode->setExactBbox( box ); + QgsBox3D box = mNode->box3D(); + box.setZMinimum( zMin ); + box.setZMaximum( zMax ); + mNode->setExactBox3D( box ); mNode->updateParentBoundingBoxesRecursively(); } @@ -170,8 +173,8 @@ QgsRuleBasedChunkLoaderFactory::QgsRuleBasedChunkLoaderFactory( const Qgs3DRende , mRootRule( rootRule->clone() ) , mLeafLevel( leafLevel ) { - const QgsAABB rootBbox = Qgs3DUtils::mapToWorldExtent( context.extent(), zMin, zMax, context.origin() ); - setupQuadtree( rootBbox, -1, leafLevel ); // negative root error means that the node does not contain anything + const QgsBox3D rootBox3D( context.extent(), zMin, zMax ); + setupQuadtree( rootBox3D, -1, leafLevel ); // negative root error means that the node does not contain anything } QgsRuleBasedChunkLoaderFactory::~QgsRuleBasedChunkLoaderFactory() = default; @@ -266,6 +269,6 @@ void QgsRuleBasedChunkedEntity::onTerrainElevationOffsetChanged( float newOffset QVector QgsRuleBasedChunkedEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const { - return QgsVectorLayerChunkedEntity::rayIntersection( activeNodes(), mTransform->matrix(), ray, context ); + return QgsVectorLayerChunkedEntity::rayIntersection( activeNodes(), mTransform->matrix(), ray, context, mMapSettings->origin() ); } /// @endcond diff --git a/src/3d/qgstessellatedpolygongeometry.cpp b/src/3d/qgstessellatedpolygongeometry.cpp index 010a66e01a5..a233476aa04 100644 --- a/src/3d/qgstessellatedpolygongeometry.cpp +++ b/src/3d/qgstessellatedpolygongeometry.cpp @@ -82,42 +82,6 @@ QgsTessellatedPolygonGeometry::QgsTessellatedPolygonGeometry( bool _withNormals, } } -void QgsTessellatedPolygonGeometry::setPolygons( const QList &polygons, const QList &featureIds, const QgsPointXY &origin, float extrusionHeight, const QList &extrusionHeightPerPolygon ) -{ - Q_ASSERT( polygons.count() == featureIds.count() ); - mTriangleIndexStartingIndices.reserve( polygons.count() ); - mTriangleIndexFids.reserve( polygons.count() ); - - QgsTessellator tessellator( origin.x(), origin.y(), mWithNormals, mInvertNormals, mAddBackFaces, false, mAddTextureCoords ); - for ( int i = 0; i < polygons.count(); ++i ) - { - Q_ASSERT( tessellator.dataVerticesCount() % 3 == 0 ); - const uint startingTriangleIndex = static_cast( tessellator.dataVerticesCount() / 3 ); - mTriangleIndexStartingIndices.append( startingTriangleIndex ); - mTriangleIndexFids.append( featureIds[i] ); - - QgsPolygon *polygon = polygons.at( i ); - const float extr = extrusionHeightPerPolygon.isEmpty() ? extrusionHeight : extrusionHeightPerPolygon.at( i ); - tessellator.addPolygon( *polygon, extr ); - } - if ( !tessellator.error().isEmpty() ) - { - QgsMessageLog::logMessage( tessellator.error(), QObject::tr( "3D" ) ); - } - - qDeleteAll( polygons ); - - const QByteArray data( ( const char * )tessellator.data().constData(), tessellator.data().count() * sizeof( float ) ); - const int nVerts = data.count() / tessellator.stride(); - - mVertexBuffer->setData( data ); - mPositionAttribute->setCount( nVerts ); - if ( mNormalAttribute ) - mNormalAttribute->setCount( nVerts ); - if ( mAddTextureCoords ) - mTextureCoordsAttribute->setCount( nVerts ); -} - void QgsTessellatedPolygonGeometry::setData( const QByteArray &vertexBufferData, int vertexCount, const QVector &triangleIndexFids, const QVector &triangleIndexStartingIndices ) { mTriangleIndexStartingIndices = triangleIndexStartingIndices; diff --git a/src/3d/qgstessellatedpolygongeometry.h b/src/3d/qgstessellatedpolygongeometry.h index b2d8fa2007e..20cb2cc9a7a 100644 --- a/src/3d/qgstessellatedpolygongeometry.h +++ b/src/3d/qgstessellatedpolygongeometry.h @@ -91,9 +91,6 @@ class QgsTessellatedPolygonGeometry : public Qt3DCore::QGeometry */ void setAddTextureCoords( bool add ) { mAddTextureCoords = add; } - //! Initializes vertex buffer from given polygons. Takes ownership of passed polygon geometries - void setPolygons( const QList &polygons, const QList &featureIds, const QgsPointXY &origin, float extrusionHeight, const QList &extrusionHeightPerPolygon = QList() ); - /** * Initializes vertex buffer (and other members) from data that were already tessellated. * This is an alternative to setPolygons() - this method does not do any expensive work in the body. diff --git a/src/3d/qgstiledscenechunkloader_p.cpp b/src/3d/qgstiledscenechunkloader_p.cpp index 8ccdc577351..d7b70faa213 100644 --- a/src/3d/qgstiledscenechunkloader_p.cpp +++ b/src/3d/qgstiledscenechunkloader_p.cpp @@ -16,6 +16,7 @@ #include "qgstiledscenechunkloader_p.h" #include "qgs3dmapsettings.h" +#include "qgs3dutils.h" #include "qgsapplication.h" #include "qgscesiumutils.h" #include "qgscoordinatetransform.h" @@ -168,32 +169,24 @@ QgsChunkLoader *QgsTiledSceneChunkLoaderFactory::createChunkLoader( QgsChunkNode return new QgsTiledSceneChunkLoader( node, mIndex, *this, mZValueScale, mZValueOffset ); } -// converts box from map coordinates to world coords (also flips [X,Y] to [X,-Z]) -static QgsAABB aabbConvert( const QgsBox3D &b0, const QgsVector3D &sceneOriginTargetCrs ) -{ - const QgsBox3D b = b0 - sceneOriginTargetCrs; - return QgsAABB( b.xMinimum(), b.zMinimum(), -b.yMaximum(), b.xMaximum(), b.zMaximum(), -b.yMinimum() ); -} - QgsChunkNode *QgsTiledSceneChunkLoaderFactory::nodeForTile( const QgsTiledSceneTile &t, const QgsChunkNodeId &nodeId, QgsChunkNode *parent ) const { QgsChunkNode *node = nullptr; if ( hasLargeBounds( t, mBoundsTransform ) ) { // use the full extent of the scene - QgsVector3D v0 = mRenderContext.mapToWorldCoordinates( QgsVector3D( mRenderContext.extent().xMinimum(), mRenderContext.extent().yMinimum(), -100 ) ); - QgsVector3D v1 = mRenderContext.mapToWorldCoordinates( QgsVector3D( mRenderContext.extent().xMaximum(), mRenderContext.extent().yMaximum(), +100 ) ); - QgsAABB aabb( v0.x(), v0.y(), v0.z(), v1.x(), v1.y(), v1.z() ); + QgsVector3D v0( mRenderContext.extent().xMinimum(), mRenderContext.extent().yMinimum(), -100 ); + QgsVector3D v1( mRenderContext.extent().xMaximum(), mRenderContext.extent().yMaximum(), +100 ); + QgsBox3D box3D( v0, v1 ); float err = std::min( 1e6, t.geometricError() ); - node = new QgsChunkNode( nodeId, aabb, err, parent ); + node = new QgsChunkNode( nodeId, box3D, err, parent ); } else { QgsBox3D box = t.boundingVolume().bounds( mBoundsTransform ); box.setZMinimum( box.zMinimum() * mZValueScale + mZValueOffset ); box.setZMaximum( box.zMaximum() * mZValueScale + mZValueOffset ); - const QgsAABB aabb = aabbConvert( box, mRenderContext.origin() ); - node = new QgsChunkNode( nodeId, aabb, t.geometricError(), parent ); + node = new QgsChunkNode( nodeId, box, t.geometricError(), parent ); } node->setRefinementProcess( t.refinementProcess() ); @@ -376,9 +369,12 @@ QVector QgsTiledSceneLayerChunkedEntity::rayIntersec #ifdef QGISDEBUG nodesAll++; #endif + + QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() ); + if ( node->entity() && - ( minDist < 0 || node->bbox().distanceFromPoint( ray.origin() ) < minDist ) && - QgsRayCastingUtils::rayBoxIntersection( ray, node->bbox() ) ) + ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && + QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) ) { #ifdef QGISDEBUG nodeUsed++; @@ -412,10 +408,10 @@ QVector QgsTiledSceneLayerChunkedEntity::rayIntersec QVariantMap vm; QgsTiledSceneTile tile = mIndex.getTile( minNode->tileId().uniqueId ); // at this point this is mostly for debugging - we may want to change/rename what's returned here - vm["node_id"] = tile.id(); - vm["node_error"] = tile.geometricError(); - vm["node_content"] = tile.resources().value( QStringLiteral( "content" ) ); - vm["triangle_index"] = minTriangleIndex; + vm[ QStringLiteral( "node_id" ) ] = tile.id(); + vm[ QStringLiteral( "node_error" ) ] = tile.geometricError(); + vm[ QStringLiteral( "node_content" ) ] = tile.resources().value( QStringLiteral( "content" ) ); + vm[ QStringLiteral( "triangle_index" ) ] = minTriangleIndex; QgsRayCastingUtils::RayHit hit( minDist, intersectionPoint, FID_NULL, vm ); result.append( hit ); } diff --git a/src/3d/qgsvectorlayerchunkloader_p.cpp b/src/3d/qgsvectorlayerchunkloader_p.cpp index 525fafd3bca..fbc219fe877 100644 --- a/src/3d/qgsvectorlayerchunkloader_p.cpp +++ b/src/3d/qgsvectorlayerchunkloader_p.cpp @@ -59,12 +59,18 @@ QgsVectorLayerChunkLoader::QgsVectorLayerChunkLoader( const QgsVectorLayerChunkL } mHandler.reset( handler ); + // only a subset of data to be queried + const QgsRectangle rect = node->box3D().toRectangle(); + // origin for coordinates of the chunk - it is kind of arbitrary, but it should be + // picked so that the coordinates are relatively small to avoid numerical precision issues + QgsVector3D chunkOrigin( rect.center().x(), rect.center().y(), 0 ); + QgsExpressionContext exprContext( Qgs3DUtils::globalProjectLayerExpressionContext( layer ) ); exprContext.setFields( layer->fields() ); mRenderContext.setExpressionContext( exprContext ); QSet attributeNames; - if ( !mHandler->prepare( mRenderContext, attributeNames ) ) + if ( !mHandler->prepare( mRenderContext, attributeNames, chunkOrigin ) ) { QgsDebugError( QStringLiteral( "Failed to prepare 3D feature handler!" ) ); return; @@ -76,9 +82,6 @@ QgsVectorLayerChunkLoader::QgsVectorLayerChunkLoader( const QgsVectorLayerChunkL QgsCoordinateTransform( layer->crs3D(), mRenderContext.crs(), mRenderContext.transformContext() ) ); req.setSubsetOfAttributes( attributeNames, layer->fields() ); - - // only a subset of data to be queried - const QgsRectangle rect = Qgs3DUtils::worldToMapExtent( node->bbox(), mRenderContext.origin() ); req.setFilterRect( rect ); // @@ -133,7 +136,7 @@ Qt3DCore::QEntity *QgsVectorLayerChunkLoader::createEntity( Qt3DCore::QEntity *p { // an empty node, so we return no entity. This tags the node as having no data and effectively removes it. // we just make sure first that its initial estimated vertical range does not affect its parents' bboxes calculation - mNode->setExactBbox( QgsAABB() ); + mNode->setExactBox3D( QgsBox3D() ); mNode->updateParentBoundingBoxesRecursively(); return nullptr; } @@ -145,10 +148,10 @@ Qt3DCore::QEntity *QgsVectorLayerChunkLoader::createEntity( Qt3DCore::QEntity *p // fix the vertical range of the node from the estimated vertical range to the true range if ( mHandler->zMinimum() != std::numeric_limits::max() && mHandler->zMaximum() != std::numeric_limits::lowest() ) { - QgsAABB box = mNode->bbox(); - box.yMin = mHandler->zMinimum(); - box.yMax = mHandler->zMaximum(); - mNode->setExactBbox( box ); + QgsBox3D box = mNode->box3D(); + box.setZMinimum( mHandler->zMinimum() ); + box.setZMaximum( mHandler->zMaximum() ); + mNode->setExactBox3D( box ); mNode->updateParentBoundingBoxesRecursively(); } @@ -165,15 +168,10 @@ QgsVectorLayerChunkLoaderFactory::QgsVectorLayerChunkLoaderFactory( const Qgs3DR , mSymbol( symbol->clone() ) , mLeafLevel( leafLevel ) { - QgsAABB rootBbox = Qgs3DUtils::mapToWorldExtent( context.extent(), zMin, zMax, context.origin() ); + QgsBox3D rootBox3D( context.extent(), zMin, zMax ); // add small padding to avoid clipping of point features located at the edge of the bounding box - rootBbox.xMin -= 1.0; - rootBbox.xMax += 1.0; - rootBbox.yMin -= 1.0; - rootBbox.yMax += 1.0; - rootBbox.zMin -= 1.0; - rootBbox.zMax += 1.0; - setupQuadtree( rootBbox, -1, leafLevel ); // negative root error means that the node does not contain anything + rootBox3D.grow( 1.0 ); + setupQuadtree( rootBox3D, -1, leafLevel ); // negative root error means that the node does not contain anything } QgsChunkLoader *QgsVectorLayerChunkLoaderFactory::createChunkLoader( QgsChunkNode *node ) const @@ -260,10 +258,10 @@ void QgsVectorLayerChunkedEntity::onTerrainElevationOffsetChanged( float newOffs QVector QgsVectorLayerChunkedEntity::rayIntersection( const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) const { - return QgsVectorLayerChunkedEntity::rayIntersection( activeNodes(), mTransform->matrix(), ray, context ); + return QgsVectorLayerChunkedEntity::rayIntersection( activeNodes(), mTransform->matrix(), ray, context, mMapSettings->origin() ); } -QVector QgsVectorLayerChunkedEntity::rayIntersection( const QList &activeNodes, const QMatrix4x4 &transformMatrix, const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ) +QVector QgsVectorLayerChunkedEntity::rayIntersection( const QList &activeNodes, const QMatrix4x4 &transformMatrix, const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context, const QgsVector3D &origin ) { Q_UNUSED( context ) QgsDebugMsgLevel( QStringLiteral( "Ray cast on vector layer" ), 2 ); @@ -284,9 +282,12 @@ QVector QgsVectorLayerChunkedEntity::rayIntersection #ifdef QGISDEBUG nodesAll++; #endif + + QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), origin ); + if ( node->entity() && - ( minDist < 0 || node->bbox().distanceFromPoint( ray.origin() ) < minDist ) && - QgsRayCastingUtils::rayBoxIntersection( ray, node->bbox() ) ) + ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && + QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) ) { #ifdef QGISDEBUG nodeUsed++; diff --git a/src/3d/qgsvectorlayerchunkloader_p.h b/src/3d/qgsvectorlayerchunkloader_p.h index d47abf10d3e..70179b4ba1f 100644 --- a/src/3d/qgsvectorlayerchunkloader_p.h +++ b/src/3d/qgsvectorlayerchunkloader_p.h @@ -129,7 +129,7 @@ class QgsVectorLayerChunkedEntity : public QgsChunkedEntity private: friend class QgsRuleBasedChunkedEntity; //! This implementation is shared between QgsVectorLayerChunkedEntity and QgsRuleBasedChunkedEntity - static QVector rayIntersection( const QList &activeNodes, const QMatrix4x4 &transformMatrix, const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context ); + static QVector rayIntersection( const QList &activeNodes, const QMatrix4x4 &transformMatrix, const QgsRayCastingUtils::Ray3D &ray, const QgsRayCastingUtils::RayCastContext &context, const QgsVector3D &origin ); Qt3DCore::QTransform *mTransform = nullptr; diff --git a/src/3d/shaders/instanced.vert b/src/3d/shaders/instanced.vert index dc7994c3099..f0f5dfed861 100644 --- a/src/3d/shaders/instanced.vert +++ b/src/3d/shaders/instanced.vert @@ -7,9 +7,9 @@ in vec3 pos; out vec3 worldPosition; out vec3 worldNormal; -uniform mat4 modelView; -uniform mat3 modelViewNormal; -uniform mat4 modelViewProjection; +uniform mat4 modelMatrix; +uniform mat3 modelNormalMatrix; +uniform mat4 mvp; uniform mat4 inst; // transform of individual object instance uniform mat4 instNormal; // should be mat3 but Qt3D only supports mat4... @@ -20,15 +20,26 @@ uniform mat4 instNormal; // should be mat3 but Qt3D only supports mat4... void main() { - // TODO: i think this is not entirely correct: the translation by "pos" works - // like this only because we assume that "inst" matrix only does translation/scale/rotation - // which all keep "w" set to 1. correctly we should use translation matrix... - vec4 offsetPos = inst * vec4(vertexPosition, 1.0) + vec4(pos, 0.0); + // vertexPosition uses XZ plane as the base plane, with Y going upwards + // and the coordinates are local to the object - worldNormal = normalize(mat3(instNormal) * vertexNormal); - worldPosition = vec3(offsetPos); + // first let's apply user defined transform for each object (translation, rotation, scaling) + vec3 vertexPositionObject = vec3(inst * vec4(vertexPosition, 1.0)); + vec3 vertexNormalObject = mat3(instNormal) * vertexNormal; - gl_Position = modelViewProjection * offsetPos; + // next let's flip axes, so we have XY plane as the base plane (like in map coordinates) + vertexPositionObject = vec3(vertexPositionObject.x, -vertexPositionObject.z, vertexPositionObject.y); + vertexNormalObject = vec3(vertexNormalObject.x, -vertexNormalObject.z, vertexNormalObject.y); + + // add offset of the object relative to the chunk's origin + vec3 vertexPositionChunk = vertexPositionObject + pos; + + // Transform position and normal to world space + worldPosition = vec3(modelMatrix * vec4(vertexPositionChunk, 1.0)); + worldNormal = normalize(modelNormalMatrix * vertexNormalObject); + + // Calculate vertex position in clip coordinates + gl_Position = mvp * vec4(vertexPositionChunk, 1.0); #ifdef CLIPPING setClipDistance(worldPosition); diff --git a/src/3d/symbols/qgsline3dsymbol_p.cpp b/src/3d/symbols/qgsline3dsymbol_p.cpp index 5a585d044d2..26eecd4487e 100644 --- a/src/3d/symbols/qgsline3dsymbol_p.cpp +++ b/src/3d/symbols/qgsline3dsymbol_p.cpp @@ -33,6 +33,7 @@ #include "qgsphongtexturedmaterialsettings.h" #include "qgsmessagelog.h" +#include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include @@ -63,7 +64,7 @@ class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler : mSymbol( static_cast< QgsLine3DSymbol *>( symbol->clone() ) ) , mSelectedIds( selectedIds ) {} - bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) override; + bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) override; void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override; void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override; @@ -86,6 +87,9 @@ class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler // inputs - generic QgsFeatureIds mSelectedIds; + //! origin (in the map coordinates) for output geometries (e.g. at the center of the chunk) + QgsVector3D mChunkOrigin; + // outputs LineData outNormal; //!< Features that are not selected LineData outSelected; //!< Features that are selected @@ -93,17 +97,19 @@ class QgsBufferedLine3DSymbolHandler : public QgsFeature3DHandler -bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) +bool QgsBufferedLine3DSymbolHandler::prepare( const Qgs3DRenderContext &, QSet &attributeNames, const QgsVector3D &chunkOrigin ) { Q_UNUSED( attributeNames ) + mChunkOrigin = chunkOrigin; + const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->materialSettings() ); - outNormal.tessellator.reset( new QgsTessellator( context.origin().x(), context.origin().y(), true, + outNormal.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false, 3, texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) ); - outSelected.tessellator.reset( new QgsTessellator( context.origin().x(), context.origin().y(), true, + outSelected.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, false, false, false, texturedMaterialSettings ? texturedMaterialSettings->requiresTextureCoordinates() : false, 3, texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) ); @@ -217,10 +223,16 @@ void QgsBufferedLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, cons Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer; renderer->setGeometry( geometry ); + // add transform (our geometry has coordinates relative to mChunkOrigin) + Qt3DCore::QTransform *tr = new Qt3DCore::QTransform; + QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D(); + tr->setTranslation( QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); + // make entity Qt3DCore::QEntity *entity = new Qt3DCore::QEntity; entity->addComponent( renderer ); entity->addComponent( mat ); + entity->addComponent( tr ); entity->setParent( parent ); if ( !selected ) @@ -243,7 +255,7 @@ class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler { } - bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) override; + bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) override; void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override; void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override; @@ -259,6 +271,9 @@ class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler // inputs - generic QgsFeatureIds mSelectedIds; + //! origin (in the map coordinates) for output geometries (e.g. at the center of the chunk) + QgsVector3D mChunkOrigin; + // outputs QgsLineVertexData outNormal; //!< Features that are not selected QgsLineVertexData outSelected; //!< Features that are selected @@ -266,14 +281,16 @@ class QgsThickLine3DSymbolHandler : public QgsFeature3DHandler -bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) +bool QgsThickLine3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) { Q_UNUSED( attributeNames ) + mChunkOrigin = chunkOrigin; + outNormal.withAdjacency = true; outSelected.withAdjacency = true; - outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->offset(), context ); - outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->offset(), context ); + outNormal.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->offset(), context, chunkOrigin ); + outSelected.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), mSymbol->offset(), context, chunkOrigin ); QSet attrs = mSymbol->dataDefinedProperties().referencedFields( context.expressionContext() ); attributeNames.unite( attrs ); @@ -374,9 +391,16 @@ void QgsThickLine3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Q renderer->setPrimitiveRestartEnabled( true ); renderer->setRestartIndexValue( 0 ); + // add transform (our geometry has coordinates relative to mChunkOrigin) + Qt3DCore::QTransform *tr = new Qt3DCore::QTransform; + tr->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), -90 ) ); // flip map (x,y,z) to world (x,z,-y) + QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D(); + tr->setTranslation( QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); + // make entity entity->addComponent( renderer ); entity->addComponent( mat ); + entity->addComponent( tr ); entity->setParent( parent ); } diff --git a/src/3d/symbols/qgslinevertexdata_p.cpp b/src/3d/symbols/qgslinevertexdata_p.cpp index 2d51a95b9b6..ddf3daa18a5 100644 --- a/src/3d/symbols/qgslinevertexdata_p.cpp +++ b/src/3d/symbols/qgslinevertexdata_p.cpp @@ -46,12 +46,13 @@ QgsLineVertexData::QgsLineVertexData() vertices << QVector3D(); } -void QgsLineVertexData::init( Qgis::AltitudeClamping clamping, Qgis::AltitudeBinding binding, float height, const Qgs3DRenderContext &context ) +void QgsLineVertexData::init( Qgis::AltitudeClamping clamping, Qgis::AltitudeBinding binding, float height, const Qgs3DRenderContext &context, const QgsVector3D &chunkOrigin ) { altClamping = clamping; altBinding = binding; baseHeight = height; renderContext = context; + origin = chunkOrigin; } QByteArray QgsLineVertexData::createVertexBuffer() @@ -135,9 +136,9 @@ void QgsLineVertexData::addLineString( const QgsLineString &lineString, float ex QgsPoint p = lineString.pointN( i ); float z = Qgs3DUtils::clampAltitude( p, altClamping, altBinding, baseHeight + extraHeightOffset, centroid, renderContext ); - vertices << QVector3D( static_cast< float >( p.x() - renderContext.origin().x() ), - z, - static_cast< float >( -( p.y() - renderContext.origin().y() ) ) ); + vertices << QVector3D( static_cast< float >( p.x() - origin.x() ), + static_cast< float >( p.y() - origin.y() ), + z ); indexes << vertices.count() - 1; } @@ -168,13 +169,13 @@ void QgsLineVertexData::addVerticalLines( const QgsLineString &lineString, float if ( withAdjacency ) indexes << vertices.count(); // add the following vertex (for adjacency) - vertices << QVector3D( static_cast< float >( p.x() - renderContext.origin().x() ), - z, - static_cast< float >( -( p.y() - renderContext.origin().y() ) ) ); + vertices << QVector3D( static_cast< float >( p.x() - origin.x() ), + static_cast< float >( p.y() - origin.y() ), + z ); indexes << vertices.count() - 1; - vertices << QVector3D( static_cast< float >( p.x() - renderContext.origin().x() ), - z2, - static_cast< float >( -( p.y() - renderContext.origin().y() ) ) ); + vertices << QVector3D( static_cast< float >( p.x() - origin.x() ), + static_cast< float >( p.y() - origin.y() ), + z2 ); indexes << vertices.count() - 1; if ( withAdjacency ) diff --git a/src/3d/symbols/qgslinevertexdata_p.h b/src/3d/symbols/qgslinevertexdata_p.h index f91b2e73028..8710ceb2cc3 100644 --- a/src/3d/symbols/qgslinevertexdata_p.h +++ b/src/3d/symbols/qgslinevertexdata_p.h @@ -80,11 +80,12 @@ struct QgsLineVertexData Qgis::AltitudeClamping altClamping = Qgis::AltitudeClamping::Relative; Qgis::AltitudeBinding altBinding = Qgis::AltitudeBinding::Vertex; float baseHeight = 0; - Qgs3DRenderContext renderContext; + Qgs3DRenderContext renderContext; // used for altitude clamping + QgsVector3D origin; // all coordinates are relative to this origin (e.g. center of the chunk) QgsLineVertexData(); - void init( Qgis::AltitudeClamping clamping, Qgis::AltitudeBinding binding, float height, const Qgs3DRenderContext &renderContext ); + void init( Qgis::AltitudeClamping clamping, Qgis::AltitudeBinding binding, float height, const Qgs3DRenderContext &renderContext, const QgsVector3D &chunkOrigin ); QByteArray createVertexBuffer(); QByteArray createIndexBuffer(); diff --git a/src/3d/symbols/qgspoint3dsymbol.cpp b/src/3d/symbols/qgspoint3dsymbol.cpp index f4e60b26952..3d7bb44f6a5 100644 --- a/src/3d/symbols/qgspoint3dsymbol.cpp +++ b/src/3d/symbols/qgspoint3dsymbol.cpp @@ -272,12 +272,9 @@ QVariant QgsPoint3DSymbol::shapeProperty( const QString &property ) const return mShapeProperties.value( property ); } -QMatrix4x4 QgsPoint3DSymbol::billboardTransform() const +float QgsPoint3DSymbol::billboardHeight() const { - QMatrix4x4 billboardTransformMatrix; - billboardTransformMatrix.translate( QVector3D( 0, mTransform.data()[13], 0 ) ); - - return billboardTransformMatrix; + return mTransform.data()[13]; } QgsAbstractMaterialSettings *QgsPoint3DSymbol::materialSettings() const diff --git a/src/3d/symbols/qgspoint3dsymbol.h b/src/3d/symbols/qgspoint3dsymbol.h index e4c6e5384e4..f002764f7e7 100644 --- a/src/3d/symbols/qgspoint3dsymbol.h +++ b/src/3d/symbols/qgspoint3dsymbol.h @@ -164,8 +164,8 @@ class _3D_EXPORT QgsPoint3DSymbol : public QgsAbstract3DSymbol SIP_NODEFAULTCTOR //! Sets transform for individual objects represented by the symbol void setTransform( const QMatrix4x4 &transform ) { mTransform = transform; } - //! Returns transform for billboards - QMatrix4x4 billboardTransform() const; + //! Returns how much the billboard should be elevated upwards + float billboardHeight() const; /** * Exports the geometries contained within the hierarchy of entity. diff --git a/src/3d/symbols/qgspoint3dsymbol_p.cpp b/src/3d/symbols/qgspoint3dsymbol_p.cpp index 92124999656..56ded96a7da 100644 --- a/src/3d/symbols/qgspoint3dsymbol_p.cpp +++ b/src/3d/symbols/qgspoint3dsymbol_p.cpp @@ -77,7 +77,7 @@ class QgsInstancedPoint3DSymbolHandler : public QgsFeature3DHandler : mSymbol( static_cast< QgsPoint3DSymbol *>( symbol->clone() ) ) , mSelectedIds( selectedIds ) {} - bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) override; + bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) override; void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override; void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override; @@ -100,16 +100,22 @@ class QgsInstancedPoint3DSymbolHandler : public QgsFeature3DHandler // inputs - generic QgsFeatureIds mSelectedIds; + //! origin (in the map coordinates) for output geometries (e.g. at the center of the chunk) + QgsVector3D mChunkOrigin; + // outputs PointData outNormal; //!< Features that are not selected PointData outSelected; //!< Features that are selected }; -bool QgsInstancedPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) +bool QgsInstancedPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) { Q_UNUSED( context ) Q_UNUSED( attributeNames ) + + mChunkOrigin = chunkOrigin; + return true; } @@ -120,7 +126,7 @@ void QgsInstancedPoint3DSymbolHandler::processFeature( const QgsFeature &feature if ( feature.geometry().isNull() ) return; - Qgs3DUtils::extractPointPositions( feature, context, mSymbol->altitudeClamping(), out.positions ); + Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions ); mFeatureCount++; } @@ -207,10 +213,17 @@ void QgsInstancedPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, co materialContext.setSelectionColor( context.selectionColor() ); QgsMaterial *mat = material( mSymbol.get(), materialContext ); + // add transform (our geometry has coordinates relative to mChunkOrigin) + Qt3DCore::QTransform *tr = new Qt3DCore::QTransform; + tr->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), -90 ) ); // flip map (x,y,z) to world (x,z,-y) + QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D(); + tr->setTranslation( QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); + // build the entity Qt3DCore::QEntity *entity = new Qt3DCore::QEntity; entity->addComponent( renderer( mSymbol.get(), out.positions ) ); entity->addComponent( mat ); + entity->addComponent( tr ); entity->setParent( parent ); // cppcheck wrongly believes entity will leak @@ -401,15 +414,15 @@ class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler : mSymbol( static_cast< QgsPoint3DSymbol * >( symbol->clone() ) ) , mSelectedIds( selectedIds ) {} - bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) override; + bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) override; void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override; void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override; private: - static void addSceneEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent ); - static void addMeshEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent, bool are_selected ); - static Qt3DCore::QTransform *transform( QVector3D position, const QgsPoint3DSymbol *symbol ); + static void addSceneEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsVector3D &chunkOrigin, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent ); + static void addMeshEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsVector3D &chunkOrigin, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent, bool are_selected ); + static Qt3DCore::QTransform *transform( QVector3D position, const QgsPoint3DSymbol *symbol, const QgsVector3D &chunkOrigin, const QgsVector3D &contextOrigin ); //! temporary data we will pass to the tessellator struct PointData @@ -424,15 +437,21 @@ class QgsModelPoint3DSymbolHandler : public QgsFeature3DHandler // inputs - generic QgsFeatureIds mSelectedIds; + //! origin (in the map coordinates) for output geometries (e.g. at the center of the chunk) + QgsVector3D mChunkOrigin; + // outputs PointData outNormal; //!< Features that are not selected PointData outSelected; //!< Features that are selected }; -bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) +bool QgsModelPoint3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) { Q_UNUSED( context ) Q_UNUSED( attributeNames ) + + mChunkOrigin = chunkOrigin; + return true; } @@ -443,7 +462,7 @@ void QgsModelPoint3DSymbolHandler::processFeature( const QgsFeature &feature, co if ( feature.geometry().isNull() ) return; - Qgs3DUtils::extractPointPositions( feature, context, mSymbol->altitudeClamping(), out.positions ); + Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions ); mFeatureCount++; } @@ -465,7 +484,7 @@ void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const { if ( selected ) { - addMeshEntities( context, out.positions, mSymbol.get(), parent, true ); + addMeshEntities( context, out.positions, mChunkOrigin, mSymbol.get(), parent, true ); } else { @@ -473,18 +492,18 @@ void QgsModelPoint3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const if ( mSymbol->shapeProperty( QStringLiteral( "overwriteMaterial" ) ).toBool() || ( mSymbol->materialSettings() && mSymbol->materialSettings()->type() != QLatin1String( "null" ) ) ) { - addMeshEntities( context, out.positions, mSymbol.get(), parent, false ); + addMeshEntities( context, out.positions, mChunkOrigin, mSymbol.get(), parent, false ); } else { - addSceneEntities( context, out.positions, mSymbol.get(), parent ); + addSceneEntities( context, out.positions, mChunkOrigin, mSymbol.get(), parent ); } } } -void QgsModelPoint3DSymbolHandler::addSceneEntities( const Qgs3DRenderContext &, const QVector &positions, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent ) +void QgsModelPoint3DSymbolHandler::addSceneEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsVector3D &chunkOrigin, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent ) { for ( const QVector3D &position : positions ) { @@ -500,7 +519,7 @@ void QgsModelPoint3DSymbolHandler::addSceneEntities( const Qgs3DRenderContext &, modelLoader->setSource( url ); entity->addComponent( modelLoader ); - entity->addComponent( transform( position, symbol ) ); + entity->addComponent( transform( position, symbol, chunkOrigin, context.origin() ) ); entity->setParent( parent ); // cppcheck wrongly believes entity will leak @@ -509,7 +528,7 @@ void QgsModelPoint3DSymbolHandler::addSceneEntities( const Qgs3DRenderContext &, } } -void QgsModelPoint3DSymbolHandler::addMeshEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent, bool are_selected ) +void QgsModelPoint3DSymbolHandler::addMeshEntities( const Qgs3DRenderContext &context, const QVector &positions, const QgsVector3D &chunkOrigin, const QgsPoint3DSymbol *symbol, Qt3DCore::QEntity *parent, bool are_selected ) { if ( positions.empty() ) return; @@ -535,7 +554,7 @@ void QgsModelPoint3DSymbolHandler::addMeshEntities( const Qgs3DRenderContext &co entity->addComponent( mesh ); entity->addComponent( mat ); - entity->addComponent( transform( position, symbol ) ); + entity->addComponent( transform( position, symbol, chunkOrigin, context.origin() ) ); entity->setParent( parent ); // cppcheck wrongly believes entity will leak @@ -544,11 +563,15 @@ void QgsModelPoint3DSymbolHandler::addMeshEntities( const Qgs3DRenderContext &co } } -Qt3DCore::QTransform *QgsModelPoint3DSymbolHandler::transform( QVector3D position, const QgsPoint3DSymbol *symbol ) +Qt3DCore::QTransform *QgsModelPoint3DSymbolHandler::transform( QVector3D position, const QgsPoint3DSymbol *symbol, const QgsVector3D &chunkOrigin, const QgsVector3D &contextOrigin ) { Qt3DCore::QTransform *tr = new Qt3DCore::QTransform; tr->setMatrix( symbol->transform() ); - tr->setTranslation( position + tr->translation() ); + // position is relative to chunkOrigin + QVector3D nodeTranslation = ( chunkOrigin - contextOrigin ).toVector3D(); + tr->setTranslation( tr->translation() + + QVector3D( position.x(), position.z(), -position.y() ) + + QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); return tr; } @@ -563,7 +586,7 @@ class QgsPoint3DBillboardSymbolHandler : public QgsFeature3DHandler : mSymbol( static_cast< QgsPoint3DSymbol * >( symbol->clone() ) ) , mSelectedIds( selectedIds ) {} - bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) override; + bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) override; void processFeature( const QgsFeature &feature, const Qgs3DRenderContext &context ) override; void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override; @@ -582,15 +605,21 @@ class QgsPoint3DBillboardSymbolHandler : public QgsFeature3DHandler // inputs - generic QgsFeatureIds mSelectedIds; + //! origin (in the map coordinates) for output geometries (e.g. at the center of the chunk) + QgsVector3D mChunkOrigin; + // outputs PointData outNormal; //!< Features that are not selected PointData outSelected; //!< Features that are selected }; -bool QgsPoint3DBillboardSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) +bool QgsPoint3DBillboardSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) { Q_UNUSED( context ) Q_UNUSED( attributeNames ) + + mChunkOrigin = chunkOrigin; + return true; } @@ -601,7 +630,7 @@ void QgsPoint3DBillboardSymbolHandler::processFeature( const QgsFeature &feature if ( feature.geometry().isNull() ) return; - Qgs3DUtils::extractPointPositions( feature, context, mSymbol->altitudeClamping(), out.positions ); + Qgs3DUtils::extractPointPositions( feature, context, mChunkOrigin, mSymbol->altitudeClamping(), out.positions ); mFeatureCount++; } @@ -646,7 +675,11 @@ void QgsPoint3DBillboardSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, co // Billboard Transform Qt3DCore::QTransform *billboardTransform = new Qt3DCore::QTransform(); - billboardTransform->setMatrix( mSymbol->billboardTransform() ); + billboardTransform->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), -90 ) ); // flip map (x,y,z) to world (x,z,-y) + QVector3D billboardHeightTranslation( 0, mSymbol->billboardHeight(), 0 ); + // our geometry has coordinates relative to mChunkOrigin + QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D(); + billboardTransform->setTranslation( billboardHeightTranslation + QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); // Build the entity Qt3DCore::QEntity *entity = new Qt3DCore::QEntity; diff --git a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp index e1eb63d5bbe..433b6ebbca5 100644 --- a/src/3d/symbols/qgspointcloud3dsymbol_p.cpp +++ b/src/3d/symbols/qgspointcloud3dsymbol_p.cpp @@ -359,7 +359,7 @@ void QgsPointCloud3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const } -std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsAABB &bbox ) +std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) { bool hasColorData = !outNormal.colors.empty(); @@ -382,11 +382,10 @@ std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudInde //factor to take account of the density of the point to calculate extension of the bounding box // with a usual value span = 128, bounding box is extended by 12.5 % on each side. double extraBoxFactor = 16 / span; - double extraX = extraBoxFactor * bbox.xExtent(); - double extraY = extraBoxFactor * bbox.yExtent(); // We keep all points in vertical direction to avoid odd triangulation if points are isolated on top - const QgsAABB extendedBBox( bbox.xMin - extraX, bbox.yMin - extraY, -std::numeric_limits::max(), bbox.xMax + extraX, bbox.yMax + extraY, std::numeric_limits::max() ); + QgsRectangle rectRelativeToChunkOrigin = ( box3D - outNormal.positionsOrigin ).toRectangle(); + rectRelativeToChunkOrigin.grow( extraBoxFactor * std::max( box3D.width(), box3D.height() ) ); PointData filteredExtraPointData; while ( parentNode.d() >= 0 ) @@ -400,7 +399,7 @@ std::vector QgsPointCloud3DSymbolHandler::getVertices( QgsPointCloudInde for ( int i = 0; i < outputParent.positions.count(); ++i ) { const QVector3D pos = outputParent.positions.at( i ) + originDifference; - if ( extendedBBox.intersects( pos.x(), pos.y(), pos.z() ) ) + if ( rectRelativeToChunkOrigin.contains( pos.x(), pos.y() ) ) { filteredExtraPointData.positions.append( pos ); vertices.push_back( pos.x() ); @@ -458,7 +457,7 @@ void QgsPointCloud3DSymbolHandler::calculateNormals( const std::vector & } } -void QgsPointCloud3DSymbolHandler::filterTriangles( const std::vector &triangleIndexes, const QgsPointCloud3DRenderContext &context, const QgsAABB &bbox ) +void QgsPointCloud3DSymbolHandler::filterTriangles( const std::vector &triangleIndexes, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) { outNormal.triangles.resize( triangleIndexes.size() * sizeof( quint32 ) ); quint32 *indexPtr = reinterpret_cast( outNormal.triangles.data() ); @@ -469,6 +468,8 @@ void QgsPointCloud3DSymbolHandler::filterTriangles( const std::vector &t float horizontalThreshold = context.symbol()->horizontalFilterThreshold(); float verticalThreshold = context.symbol()->verticalFilterThreshold(); + QgsBox3D boxRelativeToChunkOrigin = box3D - outNormal.positionsOrigin; + for ( size_t i = 0; i < triangleIndexes.size(); i += 3 ) { bool atLeastOneInBox = false; @@ -477,7 +478,7 @@ void QgsPointCloud3DSymbolHandler::filterTriangles( const std::vector &t for ( size_t j = 0; j < 3; j++ ) { QVector3D pos = outNormal.positions.at( triangleIndexes.at( i + j ) ); - atLeastOneInBox |= bbox.intersects( pos.x(), pos.y(), pos.z() ); + atLeastOneInBox |= boxRelativeToChunkOrigin.contains( pos.x(), pos.y(), pos.z() ); if ( verticalFilter || horizontalFilter ) { @@ -519,30 +520,16 @@ void QgsPointCloud3DSymbolHandler::filterTriangles( const std::vector &t } } -void QgsPointCloud3DSymbolHandler::triangulate( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsAABB &bbox ) +void QgsPointCloud3DSymbolHandler::triangulate( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ) { if ( outNormal.positions.isEmpty() ) return; - // The bbox we get is in world coordinates, we need to transform it to map coordinates - // (flip axes and add scene origin vector), but relative to the chunk's origin (subtract it) - // because that's the coordinate system used within the chunk - QgsBox3D boxRelativeToChunkOrigin( - bbox.xMin + context.origin().x() - outNormal.positionsOrigin.x(), - -bbox.zMax + context.origin().y() - outNormal.positionsOrigin.y(), - bbox.yMin + context.origin().z() - outNormal.positionsOrigin.z(), - bbox.xMax + context.origin().x() - outNormal.positionsOrigin.x(), - -bbox.zMin + context.origin().y() - outNormal.positionsOrigin.y(), - bbox.yMax + context.origin().z() - outNormal.positionsOrigin.z() ); - QgsAABB aabbRelativeToChunkOrigin( - boxRelativeToChunkOrigin.xMinimum(), boxRelativeToChunkOrigin.yMinimum(), boxRelativeToChunkOrigin.zMinimum(), - boxRelativeToChunkOrigin.xMaximum(), boxRelativeToChunkOrigin.yMaximum(), boxRelativeToChunkOrigin.zMaximum() ); - // Triangulation happens here std::unique_ptr triangulation; try { - std::vector vertices = getVertices( pc, n, context, aabbRelativeToChunkOrigin ); + std::vector vertices = getVertices( pc, n, context, box3D ); triangulation.reset( new delaunator::Delaunator( vertices ) ); } catch ( std::exception &e ) @@ -557,7 +544,7 @@ void QgsPointCloud3DSymbolHandler::triangulate( QgsPointCloudIndex *pc, const In const std::vector &triangleIndexes = triangulation->triangles; calculateNormals( triangleIndexes ); - filterTriangles( triangleIndexes, context, aabbRelativeToChunkOrigin ); + filterTriangles( triangleIndexes, context, box3D ); } std::unique_ptr QgsPointCloud3DSymbolHandler::pointCloudBlock( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloudRequest &request, const QgsPointCloud3DRenderContext &context ) diff --git a/src/3d/symbols/qgspointcloud3dsymbol_p.h b/src/3d/symbols/qgspointcloud3dsymbol_p.h index 6a3b6449b5f..acda5656ecf 100644 --- a/src/3d/symbols/qgspointcloud3dsymbol_p.h +++ b/src/3d/symbols/qgspointcloud3dsymbol_p.h @@ -48,7 +48,7 @@ class QgsPointCloud3DSymbolHandler virtual void processNode( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, PointData *output = nullptr ) = 0; // override; virtual void finalize( Qt3DCore::QEntity *parent, const QgsPointCloud3DRenderContext &context ) = 0;// override; - void triangulate( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsAABB &bbox ); + void triangulate( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); float zMinimum() const { return mZMin; } float zMaximum() const { return mZMax; } @@ -83,7 +83,7 @@ class QgsPointCloud3DSymbolHandler private: //! Returns all vertices of the node \a n, and of its parents contained in \a bbox and in an extension of this box depending of the density of the points - std::vector getVertices( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsAABB &bbox ); + std::vector getVertices( QgsPointCloudIndex *pc, const IndexedPointCloudNode &n, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); //! Calculates the normals of triangles dedined by index contained in \a triangles. Must be used only in the method triangulate(). void calculateNormals( const std::vector &triangles ); @@ -91,12 +91,12 @@ class QgsPointCloud3DSymbolHandler /** * Applies a filter on triangles to improve the rendering: * - * - keeps only triangles that have a least one point in the bounding box \a bbox + * - keeps only triangles that have a least one point in the bounding box \a box3D * - if options are selected, skips triangles with horizontal or vertical size greater than a threshold * * Must be used only in the method triangulate(). */ - void filterTriangles( const std::vector &triangleIndexes, const QgsPointCloud3DRenderContext &context, const QgsAABB &bbox ); + void filterTriangles( const std::vector &triangleIndexes, const QgsPointCloud3DRenderContext &context, const QgsBox3D &box3D ); }; class QgsSingleColorPointCloud3DSymbolHandler : public QgsPointCloud3DSymbolHandler diff --git a/src/3d/symbols/qgspolygon3dsymbol_p.cpp b/src/3d/symbols/qgspolygon3dsymbol_p.cpp index 68e7ea2139c..0ada126d3ca 100644 --- a/src/3d/symbols/qgspolygon3dsymbol_p.cpp +++ b/src/3d/symbols/qgspolygon3dsymbol_p.cpp @@ -54,7 +54,7 @@ class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler : mSymbol( static_cast< QgsPolygon3DSymbol *>( symbol->clone() ) ) , mSelectedIds( selectedIds ) {} - bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) override; + bool prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) override; void processFeature( const QgsFeature &f, const Qgs3DRenderContext &context ) override; void finalize( Qt3DCore::QEntity *parent, const Qgs3DRenderContext &context ) override; @@ -79,6 +79,9 @@ class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler // inputs - generic QgsFeatureIds mSelectedIds; + //! origin (in the map coordinates) for output geometries (e.g. at the center of the chunk) + QgsVector3D mChunkOrigin; + // outputs PolygonData outNormal; //!< Features that are not selected PolygonData outSelected; //!< Features that are selected @@ -87,18 +90,20 @@ class QgsPolygon3DSymbolHandler : public QgsFeature3DHandler }; -bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames ) +bool QgsPolygon3DSymbolHandler::prepare( const Qgs3DRenderContext &context, QSet &attributeNames, const QgsVector3D &chunkOrigin ) { outEdges.withAdjacency = true; - outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, context ); + outEdges.init( mSymbol->altitudeClamping(), mSymbol->altitudeBinding(), 0, context, chunkOrigin ); + + mChunkOrigin = chunkOrigin; const QgsPhongTexturedMaterialSettings *texturedMaterialSettings = dynamic_cast< const QgsPhongTexturedMaterialSettings * >( mSymbol->materialSettings() ); - outNormal.tessellator.reset( new QgsTessellator( context.origin().x(), context.origin().y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false, + outNormal.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false, texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(), mSymbol->renderedFacade(), texturedMaterialSettings ? texturedMaterialSettings->textureRotation() : 0 ) ); - outSelected.tessellator.reset( new QgsTessellator( context.origin().x(), context.origin().y(), true, mSymbol->invertNormals(), + outSelected.tessellator.reset( new QgsTessellator( chunkOrigin.x(), chunkOrigin.y(), true, mSymbol->invertNormals(), mSymbol->addBackFaces(), false, texturedMaterialSettings && texturedMaterialSettings->requiresTextureCoordinates(), mSymbol->renderedFacade(), @@ -253,9 +258,16 @@ void QgsPolygon3DSymbolHandler::finalize( Qt3DCore::QEntity *parent, const Qgs3D renderer->setPrimitiveRestartEnabled( true ); renderer->setRestartIndexValue( 0 ); + // add transform (our geometry has coordinates relative to mChunkOrigin) + Qt3DCore::QTransform *tr = new Qt3DCore::QTransform; + tr->setRotation( QQuaternion::fromAxisAndAngle( QVector3D( 1, 0, 0 ), -90 ) ); // flip map (x,y,z) to world (x,z,-y) + QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D(); + tr->setTranslation( QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); + // make entity entity->addComponent( renderer ); entity->addComponent( mat ); + entity->addComponent( tr ); entity->setParent( parent ); } } @@ -285,11 +297,17 @@ void QgsPolygon3DSymbolHandler::makeEntity( Qt3DCore::QEntity *parent, const Qgs Qt3DRender::QGeometryRenderer *renderer = new Qt3DRender::QGeometryRenderer; renderer->setGeometry( geometry ); + // add transform (our geometry has coordinates relative to mChunkOrigin) + Qt3DCore::QTransform *tr = new Qt3DCore::QTransform; + QVector3D nodeTranslation = ( mChunkOrigin - context.origin() ).toVector3D(); + tr->setTranslation( QVector3D( nodeTranslation.x(), nodeTranslation.z(), -nodeTranslation.y() ) ); + // make entity Qt3DCore::QEntity *entity = new Qt3DCore::QEntity; entity->setObjectName( parent->objectName() + "_CHUNK_MESH" ); entity->addComponent( renderer ); entity->addComponent( mat ); + entity->addComponent( tr ); entity->setParent( parent ); if ( !selected ) diff --git a/src/3d/terrain/qgsdemterraintileloader_p.cpp b/src/3d/terrain/qgsdemterraintileloader_p.cpp index 9b1eecae25b..bb4490c45db 100644 --- a/src/3d/terrain/qgsdemterraintileloader_p.cpp +++ b/src/3d/terrain/qgsdemterraintileloader_p.cpp @@ -123,7 +123,8 @@ Qt3DCore::QEntity *QgsDemTerrainTileLoader::createEntity( Qt3DCore::QEntity *par transform->setScale( side ); transform->setTranslation( QVector3D( x0 + half, 0, - ( y0 + half ) ) ); - mNode->setExactBbox( QgsAABB( x0, zMin * map->terrainVerticalScale(), -y0, x0 + side, zMax * map->terrainVerticalScale(), -( y0 + side ) ) ); + mNode->setExactBox3D( QgsBox3D( extent.xMinimum(), extent.yMinimum(), zMin * map->terrainVerticalScale(), + extent.xMinimum() + side, extent.yMinimum() + side, zMax * map->terrainVerticalScale() ) ); mNode->updateParentBoundingBoxesRecursively(); entity->setParent( parent ); diff --git a/src/3d/terrain/qgsflatterraingenerator.cpp b/src/3d/terrain/qgsflatterraingenerator.cpp index c6fc5d52704..09f782354b9 100644 --- a/src/3d/terrain/qgsflatterraingenerator.cpp +++ b/src/3d/terrain/qgsflatterraingenerator.cpp @@ -62,22 +62,22 @@ Qt3DCore::QEntity *FlatTerrainChunkLoader::createEntity( Qt3DCore::QEntity *pare entity->addComponent( transform ); // set up transform according to the extent covered by the quad geometry - const QgsAABB bbox = mNode->bbox(); + const QgsBox3D box3D = mNode->box3D(); + const QgsBox3D mapFullBox3D( map->extent(), box3D.zMinimum(), box3D.zMaximum() ); - const QgsAABB mapFullExtent = Qgs3DUtils::mapToWorldExtent( map->extent(), bbox.yMin, bbox.yMax, map->origin() ); + const QgsBox3D commonExtent( std::max( box3D.xMinimum(), mapFullBox3D.xMinimum() ), + std::max( box3D.yMinimum(), mapFullBox3D.yMinimum() ), + box3D.zMinimum(), + std::min( box3D.xMaximum(), mapFullBox3D.xMaximum() ), + std::min( box3D.yMaximum(), mapFullBox3D.yMaximum() ), + box3D.zMaximum() ); + const double xSide = commonExtent.width(); + const double ySide = commonExtent.height(); + const double xMin = commonExtent.xMinimum() - map->origin().x(); + const double yMin = commonExtent.yMinimum() - map->origin().y(); - const QgsAABB commonExtent = QgsAABB( std::max( bbox.xMin, mapFullExtent.xMin ), - bbox.yMin, - std::max( bbox.zMin, mapFullExtent.zMin ), - std::min( bbox.xMax, mapFullExtent.xMax ), - bbox.yMax, - std::min( bbox.zMax, mapFullExtent.zMax ) - ); - const double xSide = commonExtent.xExtent(); - const double zSide = commonExtent.zExtent(); - - transform->setScale3D( QVector3D( xSide, 1, zSide ) ); - transform->setTranslation( QVector3D( commonExtent.xMin + xSide / 2, 0, commonExtent.zMin + zSide / 2 ) ); + transform->setScale3D( QVector3D( static_cast( xSide ), 1, static_cast( ySide ) ) ); + transform->setTranslation( QVector3D( static_cast( xMin + xSide / 2 ), 0, static_cast( -( yMin + ySide / 2 ) ) ) ); createTextureComponent( entity, map->isTerrainShadingEnabled(), map->terrainShadingMaterial(), !map->layers().empty() ); diff --git a/src/3d/terrain/qgsquantizedmeshterraingenerator.cpp b/src/3d/terrain/qgsquantizedmeshterraingenerator.cpp index 0dec781e97f..a9ca6f7a67d 100644 --- a/src/3d/terrain/qgsquantizedmeshterraingenerator.cpp +++ b/src/3d/terrain/qgsquantizedmeshterraingenerator.cpp @@ -104,16 +104,14 @@ QgsQuantizedMeshTerrainChunkLoader::QgsQuantizedMeshTerrainChunkLoader( QgsTerra try { - QgsAABB bbox = node->bbox(); + QgsBox3D box3D = node->box3D(); QgsQuantizedMeshTile qmTile( content ); qmTile.removeDegenerateTriangles(); // We now know the exact height range of the tile, set it to the node. - node->setExactBbox( - QgsAABB( - // Note that in the 3D view, Y is up! - bbox.xMin, qmTile.mHeader.MinimumHeight * vertScale, bbox.zMin, - bbox.xMax, qmTile.mHeader.MaximumHeight * vertScale, bbox.zMax ) ); + box3D.setZMinimum( qmTile.mHeader.MinimumHeight * vertScale ); + box3D.setZMaximum( qmTile.mHeader.MaximumHeight * vertScale ); + node->setExactBox3D( box3D ); if ( shadingEnabled && qmTile.mNormalCoords.size() == 0 ) { @@ -308,7 +306,7 @@ QgsChunkNode *QgsQuantizedMeshTerrainGenerator::createRootNode() const { return new QgsChunkNode( {0, 0, 0}, - mRootBbox, // Given to us by setupQuadtree() + mRootBox3D, // Given to us by setupQuadtree() mMetadata->geometricErrorAtZoom( -1 ) ); } @@ -333,16 +331,12 @@ QVector QgsQuantizedMeshTerrainGenerator::createChildren( QgsChu continue; // Don't render terrain inside layer extent, but outside map extent Q_ASSERT( mTerrain ); QgsRectangle mapExtent2d = mTileCrsToMapCrs.transform( extent2d ); - QgsVector3D corner1 = mTerrain->mapSettings()->mapToWorldCoordinates( - {mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower()} ); - QgsVector3D corner2 = mTerrain->mapSettings()->mapToWorldCoordinates( - {mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper()} ); + QgsVector3D corner1( mapExtent2d.xMinimum(), mapExtent2d.yMinimum(), mMetadata->dummyZRange.lower() ); + QgsVector3D corner2( mapExtent2d.xMaximum(), mapExtent2d.yMaximum(), mMetadata->dummyZRange.upper() ); children.push_back( new QgsChunkNode( childId, - QgsAABB( - corner1.x(), corner1.y(), corner1.z(), - corner2.x(), corner2.y(), corner2.z() ), + QgsBox3D( corner1, corner2 ), mMetadata->geometricErrorAtZoom( tile.zoomLevel() ), node ) ); } diff --git a/src/3d/terrain/qgsterrainentity.cpp b/src/3d/terrain/qgsterrainentity.cpp index 74840a61a4b..2cbd6bd63ce 100644 --- a/src/3d/terrain/qgsterrainentity.cpp +++ b/src/3d/terrain/qgsterrainentity.cpp @@ -120,9 +120,12 @@ QVector QgsTerrainEntity::rayIntersection( const Qgs const QList activeNodes = this->activeNodes(); for ( QgsChunkNode *node : activeNodes ) { + + QgsAABB nodeBbox = Qgs3DUtils::mapToWorldExtent( node->box3D(), mMapSettings->origin() ); + if ( node->entity() && - ( minDist < 0 || node->bbox().distanceFromPoint( ray.origin() ) < minDist ) && - QgsRayCastingUtils::rayBoxIntersection( ray, node->bbox() ) ) + ( minDist < 0 || nodeBbox.distanceFromPoint( ray.origin() ) < minDist ) && + QgsRayCastingUtils::rayBoxIntersection( ray, nodeBbox ) ) { Qt3DRender::QGeometryRenderer *rend = node->entity()->findChild(); auto *geom = rend->geometry(); diff --git a/src/3d/terrain/qgsterraingenerator.cpp b/src/3d/terrain/qgsterraingenerator.cpp index 5fa134f917c..2abddbf0b62 100644 --- a/src/3d/terrain/qgsterraingenerator.cpp +++ b/src/3d/terrain/qgsterraingenerator.cpp @@ -15,18 +15,18 @@ #include "qgsterraingenerator.h" -#include "qgsaabb.h" #include "qgs3dmapsettings.h" #include "qgs3dutils.h" #include "qgscoordinatetransform.h" -QgsAABB QgsTerrainGenerator::rootChunkBbox( const Qgs3DMapSettings &map ) const +QgsBox3D QgsTerrainGenerator::rootChunkBox3D( const Qgs3DMapSettings &map ) const { QgsRectangle te = Qgs3DUtils::tryReprojectExtent2D( rootChunkExtent(), crs(), map.crs(), map.transformContext() ); float hMin, hMax; rootChunkHeightRange( hMin, hMax ); - return Qgs3DUtils::mapToWorldExtent( te, hMin * map.terrainVerticalScale(), hMax * map.terrainVerticalScale(), map.origin() ); + return QgsBox3D( te.xMinimum(), te.yMinimum(), hMin * map.terrainVerticalScale(), + te.xMaximum(), te.yMaximum(), hMax * map.terrainVerticalScale() ); } float QgsTerrainGenerator::rootChunkError( const Qgs3DMapSettings &map ) const diff --git a/src/3d/terrain/qgsterraingenerator.h b/src/3d/terrain/qgsterraingenerator.h index c0ef611e9f3..37c5151a703 100644 --- a/src/3d/terrain/qgsterraingenerator.h +++ b/src/3d/terrain/qgsterraingenerator.h @@ -79,8 +79,8 @@ class _3D_EXPORT QgsTerrainGenerator : public QgsQuadtreeChunkLoaderFactory //! extent of the terrain's root chunk in terrain's CRS virtual QgsRectangle rootChunkExtent() const = 0; - //! Returns bounding box of the root chunk - virtual QgsAABB rootChunkBbox( const Qgs3DMapSettings &map ) const; + //! Returns 3D box (in map coordinates) of the root chunk + virtual QgsBox3D rootChunkBox3D( const Qgs3DMapSettings &map ) const; //! Returns error of the root chunk in world coordinates virtual float rootChunkError( const Qgs3DMapSettings &map ) const; diff --git a/src/analysis/vector/geometry_checker/qgsgeometryareacheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryareacheck.cpp index 87cbb290c86..2b0346d47e1 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryareacheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryareacheck.cpp @@ -187,7 +187,7 @@ bool QgsGeometryAreaCheck::mergeWithNeighbor( const QMap geomEngine( QgsGeometryCheckerUtils::createGeomEngine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), mContext->reducedTolerance ) ); + std::unique_ptr geomEngine( QgsGeometry::createGeometryEngine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), mContext->reducedTolerance ) ); QgsAbstractGeometry *combinedGeom = geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( geom, partIdx ), &errMsg ); if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) ) { diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.cpp b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.cpp index e36541e44de..9e5bf1a47f5 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.cpp @@ -265,11 +265,6 @@ QgsGeometryCheckerUtils::LayerFeatures::iterator QgsGeometryCheckerUtils::LayerF ///////////////////////////////////////////////////////////////////////////// -std::unique_ptr QgsGeometryCheckerUtils::createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance ) -{ - return std::make_unique( geometry, tolerance ); -} - QgsAbstractGeometry *QgsGeometryCheckerUtils::getGeomPart( QgsAbstractGeometry *geom, int partIdx ) { if ( QgsGeometryCollection *collection = qgsgeometry_cast( geom ) ) diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h index 7005a5a3221..1eab4435f94 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h +++ b/src/analysis/vector/geometry_checker/qgsgeometrycheckerutils.h @@ -214,8 +214,6 @@ class ANALYSIS_EXPORT QgsGeometryCheckerUtils #ifndef SIP_RUN - static std::unique_ptr createGeomEngine( const QgsAbstractGeometry *geometry, double tolerance ); - static QgsAbstractGeometry *getGeomPart( QgsAbstractGeometry *geom, int partIdx ); static const QgsAbstractGeometry *getGeomPart( const QgsAbstractGeometry *geom, int partIdx ); diff --git a/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.cpp index 786b4b1a336..8ca11e865ed 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrycontainedcheck.cpp @@ -27,7 +27,7 @@ void QgsGeometryContainedCheck::collectErrors( const QMap geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry().constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineA( QgsGeometry::createGeometryEngine( layerFeatureA.geometry().constGet(), mContext->tolerance ) ); if ( !geomEngineA->isValid() ) { messages.append( tr( "Contained check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) ); @@ -40,7 +40,7 @@ void QgsGeometryContainedCheck::collectErrors( const QMap geomEngineB = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureB.geometry().constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineB( QgsGeometry::createGeometryEngine( layerFeatureB.geometry().constGet(), mContext->tolerance ) ); if ( !geomEngineB->isValid() ) { messages.append( tr( "Contained check failed for (%1): the geometry is invalid" ).arg( layerFeatureB.id() ) ); @@ -79,8 +79,8 @@ void QgsGeometryContainedCheck::fixError( const QMap const QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, mContext, true ); const QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, mContext, true ); - std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry().constGet(), mContext->tolerance ); - std::unique_ptr< QgsGeometryEngine > geomEngineB = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureB.geometry().constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineA( QgsGeometry::createGeometryEngine( layerFeatureA.geometry().constGet(), mContext->tolerance ) ); + std::unique_ptr< QgsGeometryEngine > geomEngineB( QgsGeometry::createGeometryEngine( layerFeatureB.geometry().constGet(), mContext->tolerance ) ); if ( !( geomEngineB->contains( layerFeatureA.geometry().constGet() ) && !geomEngineA->contains( layerFeatureB.geometry().constGet() ) ) ) { diff --git a/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.cpp index 5d7fa5466fe..3ae9c8c9d30 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryduplicatecheck.cpp @@ -51,7 +51,7 @@ void QgsGeometryDuplicateCheck::collectErrors( const QMap geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geomA.constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineA( QgsGeometry::createGeometryEngine( geomA.constGet(), mContext->tolerance, Qgis::GeosCreationFlags() ) ); if ( !geomEngineA->isValid() ) { messages.append( tr( "Duplicate check failed for (%1): the geometry is invalid" ).arg( layerFeatureA.id() ) ); @@ -105,7 +105,7 @@ void QgsGeometryDuplicateCheck::fixError( const QMap else if ( method == RemoveDuplicates ) { const QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, mContext, true ); - std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( layerFeatureA.geometry().constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineA( QgsGeometry::createGeometryEngine( layerFeatureA.geometry().constGet(), mContext->tolerance ) ); QgsGeometryDuplicateCheckError *duplicateError = static_cast( error ); const QMap> duplicates = duplicateError->duplicates(); diff --git a/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp index 85b9896546f..07e103256d3 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryfollowboundariescheck.cpp @@ -57,7 +57,7 @@ void QgsGeometryFollowBoundariesCheck::collectErrors( const QMapclone() ); geomt.transform( crst ); - std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( geomt.constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngine( QgsGeometry::createGeometryEngine( geomt.constGet(), mContext->tolerance ) ); // Get potential reference features QgsRectangle searchBounds = geomt.constGet()->boundingBox(); @@ -79,7 +79,7 @@ void QgsGeometryFollowBoundariesCheck::collectErrors( const QMap refgeomEngine( QgsGeometryCheckerUtils::createGeomEngine( refGeom, mContext->tolerance ) ); + std::unique_ptr refgeomEngine( QgsGeometry::createGeometryEngine( refGeom, mContext->tolerance ) ); const QgsGeometry reducedRefGeom( refgeomEngine->buffer( -mContext->tolerance, 0 ) ); if ( !( geomEngine->contains( reducedRefGeom.constGet() ) || geomEngine->disjoint( reducedRefGeom.constGet() ) ) ) { diff --git a/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp index 3ae73119960..5e5d1cee7f4 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrygapcheck.cpp @@ -74,12 +74,12 @@ void QgsGeometryGapCheck::collectErrors( const QMap & allowedGaps.append( gg ); } - std::unique_ptr< QgsGeometryEngine > allowedGapsEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > allowedGapsEngine( QgsGeometry::createGeometryEngine( nullptr, mContext->tolerance ) ); // Create union of allowed gaps QString errMsg; allowedGapsGeom.reset( allowedGapsEngine->combine( allowedGaps, &errMsg ) ); - allowedGapsGeomEngine = QgsGeometryCheckerUtils::createGeomEngine( allowedGapsGeom.get(), mContext->tolerance ); + allowedGapsGeomEngine.reset( QgsGeometry::createGeometryEngine( allowedGapsGeom.get(), mContext->tolerance ) ); allowedGapsGeomEngine->prepareGeometry(); } @@ -102,7 +102,7 @@ void QgsGeometryGapCheck::collectErrors( const QMap & return; } - std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( nullptr, mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngine( QgsGeometry::createGeometryEngine( nullptr, mContext->tolerance ) ); // Create union of geometry QString errMsg; @@ -114,7 +114,7 @@ void QgsGeometryGapCheck::collectErrors( const QMap & } // Get envelope of union - geomEngine = QgsGeometryCheckerUtils::createGeomEngine( unionGeom.get(), mContext->tolerance ); + geomEngine.reset( QgsGeometry::createGeometryEngine( unionGeom.get(), mContext->tolerance ) ); geomEngine->prepareGeometry(); std::unique_ptr envelope( geomEngine->envelope( &errMsg ) ); if ( !envelope ) @@ -124,13 +124,13 @@ void QgsGeometryGapCheck::collectErrors( const QMap & } // Buffer envelope - geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance ); + geomEngine.reset( QgsGeometry::createGeometryEngine( envelope.get(), mContext->tolerance ) ); geomEngine->prepareGeometry(); QgsAbstractGeometry *bufEnvelope = geomEngine->buffer( 2, 0, Qgis::EndCapStyle::Square, Qgis::JoinStyle::Miter, 4. ); //#spellok //#spellok envelope.reset( bufEnvelope ); // Compute difference between envelope and union to obtain gap polygons - geomEngine = QgsGeometryCheckerUtils::createGeomEngine( envelope.get(), mContext->tolerance ); + geomEngine.reset( QgsGeometry::createGeometryEngine( envelope.get(), mContext->tolerance ) ); geomEngine->prepareGeometry(); std::unique_ptr diffGeom( geomEngine->difference( unionGeom.get(), &errMsg ) ); if ( !diffGeom ) @@ -162,7 +162,7 @@ void QgsGeometryGapCheck::collectErrors( const QMap & // Get neighboring polygons QMap neighboringIds; const QgsGeometryCheckerUtils::LayerFeatures layerFeatures( featurePools, featureIds.keys(), gapAreaBBox, compatibleGeometryTypes(), mContext ); - std::unique_ptr< QgsGeometryEngine > gapGeomEngine = QgsGeometryCheckerUtils::createGeomEngine( gapGeom, mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > gapGeomEngine( QgsGeometry::createGeometryEngine( gapGeom, mContext->tolerance ) ); gapGeomEngine->prepareGeometry(); for ( const QgsGeometryCheckerUtils::LayerFeature &layerFeature : layerFeatures ) { @@ -407,7 +407,7 @@ bool QgsGeometryGapCheck::mergeWithNeighbor( const QMaptransform( ct, Qgis::TransformDirection::Reverse ); const QgsGeometry mergeFeatureGeom = mergeFeature.geometry(); const QgsAbstractGeometry *mergeGeom = mergeFeatureGeom.constGet(); - std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( errLayerGeom.get(), 0 ); + std::unique_ptr< QgsGeometryEngine > geomEngine( QgsGeometry::createGeometryEngine( errLayerGeom.get(), 0 ) ); std::unique_ptr combinedGeom( geomEngine->combine( QgsGeometryCheckerUtils::getGeomPart( mergeGeom, mergePartIdx ), &errMsg ) ); if ( !combinedGeom || combinedGeom->isEmpty() || !QgsWkbTypes::isSingleType( combinedGeom->wkbType() ) ) { diff --git a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp index 851944d52ac..d23e81069be 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrymissingvertexcheck.cpp @@ -130,17 +130,17 @@ void QgsGeometryMissingVertexCheck::processPolygon( const QgsCurvePolygon *polyg const QgsFeature ¤tFeature = layerFeature.feature(); std::unique_ptr boundaries = std::make_unique(); - std::unique_ptr< QgsGeometryEngine > geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->exteriorRing()->clone(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngine( QgsGeometry::createGeometryEngine( polygon->exteriorRing()->clone(), mContext->tolerance ) ); boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) ); const int numRings = polygon->numInteriorRings(); for ( int i = 0; i < numRings; ++i ) { - geomEngine = QgsGeometryCheckerUtils::createGeomEngine( polygon->interiorRing( i ), mContext->tolerance ); + geomEngine.reset( QgsGeometry::createGeometryEngine( polygon->interiorRing( i ), mContext->tolerance ) ); boundaries->addGeometry( geomEngine->buffer( mContext->tolerance, 5 ) ); } - geomEngine = QgsGeometryCheckerUtils::createGeomEngine( boundaries.get(), mContext->tolerance ); + geomEngine.reset( QgsGeometry::createGeometryEngine( boundaries.get(), mContext->tolerance ) ); geomEngine->prepareGeometry(); const QgsFeatureIds fids = featurePool->getIntersects( boundaries->boundingBox() ); diff --git a/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp index b5504f0c07e..055f8f4d948 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryoverlapcheck.cpp @@ -44,7 +44,7 @@ void QgsGeometryOverlapCheck::collectErrors( const QMap geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geomA.constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineA( QgsGeometry::createGeometryEngine( geomA.constGet(), mContext->tolerance ) ); geomEngineA->prepareGeometry(); if ( !geomEngineA->isValid() ) { @@ -112,7 +112,7 @@ void QgsGeometryOverlapCheck::fixError( const QMap &f const QgsGeometryCheckerUtils::LayerFeature layerFeatureA( featurePoolA, featureA, mContext, true ); const QgsGeometryCheckerUtils::LayerFeature layerFeatureB( featurePoolB, featureB, mContext, true ); const QgsGeometry geometryA = layerFeatureA.geometry(); - std::unique_ptr< QgsGeometryEngine > geomEngineA = QgsGeometryCheckerUtils::createGeomEngine( geometryA.constGet(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEngineA( QgsGeometry::createGeometryEngine( geometryA.constGet(), mContext->tolerance ) ); geomEngineA->prepareGeometry(); const QgsGeometry geometryB = layerFeatureB.geometry(); @@ -153,7 +153,7 @@ void QgsGeometryOverlapCheck::fixError( const QMap &f } else if ( method == Subtract ) { - std::unique_ptr< QgsGeometryEngine > geomEngineDiffA = QgsGeometryCheckerUtils::createGeomEngine( geometryA.constGet(), 0 ); + std::unique_ptr< QgsGeometryEngine > geomEngineDiffA( QgsGeometry::createGeometryEngine( geometryA.constGet(), 0 ) ); std::unique_ptr< QgsAbstractGeometry > diff1( geomEngineDiffA->difference( interPart, &errMsg ) ); if ( !diff1 || diff1->isEmpty() ) { @@ -163,7 +163,7 @@ void QgsGeometryOverlapCheck::fixError( const QMap &f { QgsGeometryCheckerUtils::filter1DTypes( diff1.get() ); } - std::unique_ptr< QgsGeometryEngine > geomEngineDiffB = QgsGeometryCheckerUtils::createGeomEngine( geometryB.constGet(), 0 ); + std::unique_ptr< QgsGeometryEngine > geomEngineDiffB( QgsGeometry::createGeometryEngine( geometryB.constGet(), 0 ) ); std::unique_ptr< QgsAbstractGeometry > diff2( geomEngineDiffB->difference( interPart, &errMsg ) ); if ( !diff2 || diff2->isEmpty() ) { diff --git a/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.cpp index 419fd7dec23..96fcd50071e 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometrypointinpolygoncheck.cpp @@ -44,7 +44,7 @@ void QgsGeometryPointInPolygonCheck::collectErrors( const QMap testGeomEngine = QgsGeometryCheckerUtils::createGeomEngine( testGeom, mContext->reducedTolerance ); + std::unique_ptr< QgsGeometryEngine > testGeomEngine( QgsGeometry::createGeometryEngine( testGeom, mContext->reducedTolerance ) ); if ( !testGeomEngine->isValid() ) { messages.append( tr( "Point in polygon check failed for (%1): the geometry is invalid" ).arg( checkFeature.id() ) ); diff --git a/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp b/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp index 7483c303790..ca1c045470a 100644 --- a/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp +++ b/src/analysis/vector/geometry_checker/qgsgeometryselfintersectioncheck.cpp @@ -191,8 +191,8 @@ void QgsGeometrySelfIntersectionCheck::fixError( const QMapsetExteriorRing( ringGeom2.release() ); // Reassing interiors as necessary - std::unique_ptr< QgsGeometryEngine > geomEnginePoly1 = QgsGeometryCheckerUtils::createGeomEngine( poly, mContext->tolerance ); - std::unique_ptr< QgsGeometryEngine > geomEnginePoly2 = QgsGeometryCheckerUtils::createGeomEngine( poly2.get(), mContext->tolerance ); + std::unique_ptr< QgsGeometryEngine > geomEnginePoly1( QgsGeometry::createGeometryEngine( poly, mContext->tolerance ) ); + std::unique_ptr< QgsGeometryEngine > geomEnginePoly2( QgsGeometry::createGeometryEngine( poly2.get(), mContext->tolerance ) ); for ( int n = poly->numInteriorRings(), i = n - 1; i >= 0; --i ) { if ( !geomEnginePoly1->contains( poly->interiorRing( i ) ) ) diff --git a/src/app/3d/qgs3dmaptoolidentify.cpp b/src/app/3d/qgs3dmaptoolidentify.cpp index e65d1dae21a..0d924bea287 100644 --- a/src/app/3d/qgs3dmaptoolidentify.cpp +++ b/src/app/3d/qgs3dmaptoolidentify.cpp @@ -25,6 +25,7 @@ #include "qgisapp.h" #include "qgsmapcanvas.h" +#include "qgscoordinateutils.h" #include "qgsmaptoolidentifyaction.h" #include "qgspointcloudlayer.h" @@ -63,10 +64,8 @@ void Qgs3DMapToolIdentify::mouseReleaseEvent( QMouseEvent *event ) const QgsRay3D ray = Qgs3DUtils::rayFromScreenPoint( event->pos(), mCanvas->size(), mCanvas->cameraController()->camera() ); QHash> allHits = Qgs3DUtils::castRay( mCanvas->scene(), ray, QgsRayCastingUtils::RayCastContext( false, mCanvas->size(), mCanvas->cameraController()->camera()->farPlane() ) ); - QHash> pointCloudResults; - QHash> tiledSceneResults; - - QList identifyResults; + QList tiledSceneIdentifyResults; + QList pointCloudIdentifyResults; QgsMapToolIdentifyAction *identifyTool2D = QgisApp::instance()->identifyMapTool(); identifyTool2D->clearResults(); @@ -83,26 +82,53 @@ void Qgs3DMapToolIdentify::mouseReleaseEvent( QMouseEvent *event ) identifyTool2D->showResultsForFeature( vlayer, hit.fid, pt ); showTerrainResults = false; } - // We need to restructure point cloud layer results to display them later + // We need to restructure point cloud layer results to display them later. We may have multiple hits for each layer. else if ( QgsPointCloudLayer *pclayer = qobject_cast( it->first ) ) { - pointCloudResults[ pclayer ] = QVector(); + QVector pointCloudResults; for ( const QgsRayCastingUtils::RayHit &hit : it->second ) { - pointCloudResults[ pclayer ].append( hit.attributes ); + pointCloudResults.append( hit.attributes ); } + identifyTool2D->fromPointCloudIdentificationToIdentifyResults( pclayer, pointCloudResults, pointCloudIdentifyResults ); } else if ( QgsTiledSceneLayer *tslayer = qobject_cast( it->first ) ) { - tiledSceneResults[ tslayer ] = QVector(); - for ( const QgsRayCastingUtils::RayHit &hit : it->second ) + Q_UNUSED( tslayer ) + // We are only handling a single hit for each layer + const QgsRayCastingUtils::RayHit hit = it->second.first(); + const QgsVector3D mapCoords = Qgs3DUtils::worldToMapCoordinates( hit.pos, mCanvas->mapSettings()->origin() ); + + QMap< QString, QString > derivedAttributes; + QString x; + QString y; + QgsCoordinateUtils::formatCoordinatePartsForProject( + QgsProject::instance(), + QgsPointXY( mapCoords.x(), mapCoords.y() ), + QgsProject::instance()->crs(), + 6, x, y + ); + + derivedAttributes.insert( tr( "(clicked coordinate X)" ), x ); + derivedAttributes.insert( tr( "(clicked coordinate Y)" ), y ); + derivedAttributes.insert( tr( "(clicked coordinate Z)" ), QLocale().toString( mapCoords.z(), 'f' ) ); + + const QList keys = hit.attributes.keys(); + for ( const QString &key : keys ) { - tiledSceneResults[ tslayer ].append( hit.attributes ); + derivedAttributes[key] = hit.attributes[key].toString(); } + QString nodeId = derivedAttributes[ QStringLiteral( "node_id" ) ]; + // only derived attributes are supported for now, so attributes is empty + QgsMapToolIdentify::IdentifyResult res( it->first, nodeId, {}, derivedAttributes ); + tiledSceneIdentifyResults.append( res ); } } - // We only handle terrain results if there were no vector layer results + // We only handle terrain results if there were no vector layer results because: + // a. terrain results will overwrite other existing results. + // b. terrain results use 2d identify logic and may contain results from vector layers that + // are not actually rendered on the terrain as they have a 3d renderer set. if ( showTerrainResults && allHits.contains( nullptr ) ) { const QgsRayCastingUtils::RayHit hit = allHits.value( nullptr ).first(); @@ -139,33 +165,12 @@ void Qgs3DMapToolIdentify::mouseReleaseEvent( QMouseEvent *event ) identifyTool2D->identifyAndShowResults( QgsGeometry::fromPointXY( mapPointCanvas2D ), searchRadiusCanvas2D ); } + // We need to show other layer type results AFTER terrain results so they don't get overwritten + identifyTool2D->showIdentifyResults( tiledSceneIdentifyResults ); + // Finally add all point cloud layers' results - for ( auto it = pointCloudResults.constKeyValueBegin(); it != pointCloudResults.constKeyValueEnd(); ++it ) - { - QgsMapToolIdentify identifyTool( nullptr ); - identifyTool.fromPointCloudIdentificationToIdentifyResults( it->first, it->second, identifyResults ); - identifyTool2D->showIdentifyResults( identifyResults ); - } - - for ( auto it = tiledSceneResults.constKeyValueBegin(); it != tiledSceneResults.constKeyValueEnd(); ++it ) - { - // for the whole layer - for ( const QVariantMap &hitAttributes : it->second ) - { - QMap derivedAttributes; - const QList keys = hitAttributes.keys(); - for ( const QString &key : keys ) - { - derivedAttributes[key] = hitAttributes[key].toString(); - } - QString nodeId = hitAttributes["node_id"].toString(); - QgsMapToolIdentify::IdentifyResult res( it->first, nodeId, QMap(), derivedAttributes ); - identifyResults.append( res ); - } - - identifyTool2D->showIdentifyResults( identifyResults ); - } - + // We add those last as the list can be quite big. + identifyTool2D->showIdentifyResults( pointCloudIdentifyResults ); } void Qgs3DMapToolIdentify::activate() diff --git a/src/app/layout/qgslayoutdesignerdialog.cpp b/src/app/layout/qgslayoutdesignerdialog.cpp index 7926ddbf621..f1b38152d13 100644 --- a/src/app/layout/qgslayoutdesignerdialog.cpp +++ b/src/app/layout/qgslayoutdesignerdialog.cpp @@ -2296,7 +2296,7 @@ void QgsLayoutDesignerDialog::exportToRaster() QgsLayoutExporter::ImageExportSettings settings; QSize imageSize; - if ( !getRasterExportSettings( settings, imageSize ) ) + if ( !getRasterExportSettings( settings, imageSize, fileNExt.second ) ) return; mView->setPaintingEnabled( false ); @@ -2999,7 +2999,7 @@ void QgsLayoutDesignerDialog::exportAtlasToRaster() QgsLayoutExporter::ImageExportSettings settings; QSize imageSize; - if ( !getRasterExportSettings( settings, imageSize ) ) + if ( !getRasterExportSettings( settings, imageSize, format ) ) return; mView->setPaintingEnabled( false ); @@ -3532,7 +3532,7 @@ void QgsLayoutDesignerDialog::exportReportToRaster() QgsLayoutExporter::ImageExportSettings settings; QSize imageSize; - if ( !getRasterExportSettings( settings, imageSize ) ) + if ( !getRasterExportSettings( settings, imageSize, fileNExt.second ) ) return; mView->setPaintingEnabled( false ); @@ -4336,7 +4336,7 @@ bool QgsLayoutDesignerDialog::showFileSizeWarning() return true; } -bool QgsLayoutDesignerDialog::getRasterExportSettings( QgsLayoutExporter::ImageExportSettings &settings, QSize &imageSize ) +bool QgsLayoutDesignerDialog::getRasterExportSettings( QgsLayoutExporter::ImageExportSettings &settings, QSize &imageSize, const QString &fileExtension ) { QSizeF maxPageSize; bool hasUniformPageSizes = false; @@ -4366,7 +4366,7 @@ bool QgsLayoutDesignerDialog::getRasterExportSettings( QgsLayoutExporter::ImageE antialias = mLayout->customProperty( QStringLiteral( "imageAntialias" ), true ).toBool(); } - QgsLayoutImageExportOptionsDialog imageDlg( this ); + QgsLayoutImageExportOptionsDialog imageDlg( this, fileExtension ); imageDlg.setImageSize( maxPageSize ); imageDlg.setResolution( dpi ); imageDlg.setCropToContents( cropToContents ); @@ -4375,6 +4375,7 @@ bool QgsLayoutDesignerDialog::getRasterExportSettings( QgsLayoutExporter::ImageE imageDlg.setGenerateWorldFile( mLayout->customProperty( QStringLiteral( "exportWorldFile" ), false ).toBool() ); imageDlg.setAntialiasing( antialias ); imageDlg.setOpenAfterExporting( QgsLayoutExporter::settingOpenAfterExportingImage->value() ); + imageDlg.setQuality( QgsLayoutExporter::settingImageQuality->value() ); if ( !imageDlg.exec() ) return false; @@ -4409,6 +4410,12 @@ bool QgsLayoutDesignerDialog::getRasterExportSettings( QgsLayoutExporter::ImageE else settings.flags &= ~QgsLayoutRenderContext::FlagAntialiasing; + settings.quality = imageDlg.quality(); + if ( settings.quality != -1 ) + { + QgsLayoutExporter::settingImageQuality->setValue( imageDlg.quality() ); + } + return true; } diff --git a/src/app/layout/qgslayoutdesignerdialog.h b/src/app/layout/qgslayoutdesignerdialog.h index 9868e73f37f..7c6ee723c44 100644 --- a/src/app/layout/qgslayoutdesignerdialog.h +++ b/src/app/layout/qgslayoutdesignerdialog.h @@ -564,7 +564,7 @@ class QgsLayoutDesignerDialog: public QMainWindow, public Ui::QgsLayoutDesignerB void showForceVectorWarning(); bool showFileSizeWarning(); - bool getRasterExportSettings( QgsLayoutExporter::ImageExportSettings &settings, QSize &imageSize ); + bool getRasterExportSettings( QgsLayoutExporter::ImageExportSettings &settings, QSize &imageSize, const QString &fileExtension ); bool getSvgExportSettings( QgsLayoutExporter::SvgExportSettings &settings ); bool getPdfExportSettings( QgsLayoutExporter::PdfExportSettings &settings, bool allowGeospatialPdfExport = true, const QString &geospatialPdfReason = QString() ); diff --git a/src/app/mesh/qgsmaptooleditmeshframe.cpp b/src/app/mesh/qgsmaptooleditmeshframe.cpp index b6677813ffb..3d6a27c145c 100644 --- a/src/app/mesh/qgsmaptooleditmeshframe.cpp +++ b/src/app/mesh/qgsmaptooleditmeshframe.cpp @@ -16,10 +16,12 @@ #include "qgsmaptooleditmeshframe.h" #include +#include #include "qgis.h" #include "qgisapp.h" #include "qgsapplication.h" +#include "qgsstatusbar.h" #include "qgsadvanceddigitizingdockwidget.h" #include "qgsdoublespinbox.h" @@ -45,6 +47,7 @@ #include "qgsmeshselectbyexpressiondialog.h" #include "qgsmaptoolidentify.h" #include "qgsidentifymenu.h" +#include "qgsdistancearea.h" // @@ -331,6 +334,8 @@ QgsMapToolEditMeshFrame::QgsMapToolEditMeshFrame( QgsMapCanvas *canvas ) createZValueWidget(); } ); + connect( this, &QgsMapToolEditMeshFrame::selectionChange, this, &QgsMapToolEditMeshFrame::updateStatusBarMessage ); + setAutoSnapEnabled( true ); } @@ -2841,3 +2846,65 @@ void QgsMapToolEditMeshFrame::showSelectByExpressionDialog() connect( dialog, &QgsMeshSelectByExpressionDialog::select, this, &QgsMapToolEditMeshFrame::selectByExpression ); connect( dialog, &QgsMeshSelectByExpressionDialog::zoomToSelected, this, &QgsMapToolEditMeshFrame::onZoomToSelected ); } + +void QgsMapToolEditMeshFrame::updateStatusBarMessage() const +{ + if ( ! mSelectedVertices.isEmpty() ) + { + QString message; + if ( mSelectedVertices.count() == 1 ) + { + const QgsMesh &mesh = *mCurrentLayer->nativeMesh(); + const int vertexId = mSelectedVertices.firstKey(); + const QgsMeshVertex vertex = mesh.vertex( vertexId ); + + message = tr( "Selected mesh vertex ID: %1 at x: %2 y: %3 z: %4." ).arg( vertexId ).arg( QLocale().toString( vertex.x(), 'f' ) ).arg( QLocale().toString( vertex.y(), 'f' ) ).arg( QLocale().toString( vertex.z(), 'f' ) ); + } + else if ( mSelectedVertices.count() == 2 ) + { + const QgsMesh &mesh = *mCurrentLayer->nativeMesh(); + const int vertexId1 = mSelectedVertices.firstKey(); + const int vertexId2 = mSelectedVertices.lastKey(); + const QgsMeshVertex vertex1 = mesh.vertex( vertexId1 ); + const QgsMeshVertex vertex2 = mesh.vertex( vertexId2 ); + + QString formattedDistance; + double distance; + + // if crs is valid calculate using QgsDistanceArea otherwise calculate just as distance + if ( mCurrentLayer->crs().isValid() ) + { + QgsDistanceArea distArea = QgsDistanceArea(); + distArea.setSourceCrs( mCurrentLayer->crs(), QgsProject::instance()->transformContext() ); + distArea.setEllipsoid( QgsProject::instance()->ellipsoid() ); + try + { + distance = distArea.measureLine( QgsPointXY( vertex1 ), QgsPointXY( vertex2 ) ); + distance = distArea.convertLengthMeasurement( distance, QgsProject::instance()->distanceUnits() ); + formattedDistance = distArea.formatDistance( distance, 6, QgsProject::instance()->distanceUnits() ); + } + catch ( QgsCsException & ) {} + } + + if ( formattedDistance.isEmpty() ) + { + distance = vertex1.distance( vertex2 ); + formattedDistance = QLocale().toString( distance, 'f' ); + } + + const double zDiff = vertex2.z() - vertex1.z(); + + message = tr( "Selected mesh vertices IDs: %1 and %2 with distance %3 and dZ %4." ).arg( vertexId1 ).arg( vertexId2 ).arg( formattedDistance ).arg( QLocale().toString( zDiff, 'f' ) ); + } + else if ( mSelectedVertices.count() > 2 ) + { + message = tr( "Selected %1 mesh vertices." ).arg( mSelectedVertices.count() ); + } + + QgisApp::instance()->statusBarIface()->showMessage( message ); + } + else + { + QgisApp::instance()->statusBarIface()->clearMessage(); + } +} diff --git a/src/app/mesh/qgsmaptooleditmeshframe.h b/src/app/mesh/qgsmaptooleditmeshframe.h index 850b2a876bb..7e6a66a77b2 100644 --- a/src/app/mesh/qgsmaptooleditmeshframe.h +++ b/src/app/mesh/qgsmaptooleditmeshframe.h @@ -166,6 +166,7 @@ class APP_EXPORT QgsMapToolEditMeshFrame : public QgsMapToolAdvancedDigitizing void onZoomToSelected(); void reindexMesh(); void onUndoRedo(); + void updateStatusBarMessage() const; private: diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 750cbcaaa72..808540650d2 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -30,8 +30,6 @@ set(QGIS_CORE_SRCS ${CMAKE_SOURCE_DIR}/external/nmea/time.c ${CMAKE_SOURCE_DIR}/external/nmea/tok.c - ${CMAKE_SOURCE_DIR}/external/meshOptimizer/simplifier.cpp - ${FLEX_QgsExpressionLexer_OUTPUTS} ${BISON_QgsExpressionParser_OUTPUTS} ${FLEX_QgsSqlStatementLexer_OUTPUTS} @@ -365,6 +363,7 @@ set(QGIS_CORE_SRCS stac/qgsstacasset.cpp stac/qgsstaccatalog.cpp stac/qgsstaccollection.cpp + stac/qgsstaccollections.cpp stac/qgsstacconnection.cpp stac/qgsstaccontroller.cpp stac/qgsstacdataitems.cpp @@ -1960,6 +1959,7 @@ set(QGIS_CORE_HDRS stac/qgsstacasset.h stac/qgsstaccatalog.h stac/qgsstaccollection.h + stac/qgsstaccollections.h stac/qgsstacconnection.h stac/qgsstaccontroller.h stac/qgsstacdataitems.h @@ -2339,6 +2339,20 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}) add_library(qgis_core ${LIBRARY_TYPE} ${QGIS_CORE_SRCS} ${QGIS_CORE_HDRS} ${QGIS_CORE_PRIVATE_HDRS} ${IMAGE_RCCS}) +# Add meshoptimizer +if(WITH_INTERNAL_MESHOPTIMIZER) + target_sources(qgis_core PRIVATE + ${CMAKE_SOURCE_DIR}/external/meshOptimizer/simplifier.cpp + ) + + target_include_directories(qgis_core PRIVATE + ${CMAKE_SOURCE_DIR}/external/meshOptimizer + ) +else() + find_package(meshoptimizer CONFIG REQUIRED) + target_link_libraries(qgis_core meshoptimizer::meshoptimizer) +endif() + # require c++17 target_compile_features(qgis_core PRIVATE cxx_std_17) @@ -2434,7 +2448,6 @@ target_include_directories(qgis_core PUBLIC ${CMAKE_SOURCE_DIR}/external/kdbush/include ${CMAKE_SOURCE_DIR}/external/nmea ${CMAKE_SOURCE_DIR}/external/rtree/include - ${CMAKE_SOURCE_DIR}/external/meshOptimizer ${CMAKE_SOURCE_DIR}/external/tinygltf ) diff --git a/src/core/auth/qgsauthconfigurationstorage.h b/src/core/auth/qgsauthconfigurationstorage.h index c4aca881ecd..f3991e5c689 100644 --- a/src/core/auth/qgsauthconfigurationstorage.h +++ b/src/core/auth/qgsauthconfigurationstorage.h @@ -95,9 +95,10 @@ class CORE_EXPORT QgsAuthConfigurationStorage: public QObject /** * Initializes the storage. * \returns TRUE if the storage was successfully initialized, FALSE otherwise. - * If the storage is already initialized, this method does nothing and returns TRUE. - * \note The default implementation does nothing and returns TRUE. - * This method is called by the authentication manager when the storage is added to the manager. + * If the storage is already initialized, this method does nothing and returns TRUE. + * + * \note The default implementation does nothing and returns TRUE. + * This method is called by the authentication manager when the storage is added to the manager. */ virtual bool initialize() { return true; } diff --git a/src/core/expression/qgsexpression.cpp b/src/core/expression/qgsexpression.cpp index 528c3616ff5..68c9e0500ed 100644 --- a/src/core/expression/qgsexpression.cpp +++ b/src/core/expression/qgsexpression.cpp @@ -1022,10 +1022,10 @@ QString QgsExpression::formatPreviewString( const QVariant &value, const bool ht const QString startToken = htmlOutput ? QStringLiteral( "<" ) : QStringLiteral( "<" ); const QString endToken = htmlOutput ? QStringLiteral( ">" ) : QStringLiteral( ">" ); - if ( value.userType() == qMetaTypeId< QgsGeometry>() ) + QgsGeometry geom = QgsExpressionUtils::getGeometry( value, nullptr ); + if ( !geom.isNull() ) { //result is a geometry - QgsGeometry geom = value.value(); if ( geom.isNull() ) return startToken + tr( "empty geometry" ) + endToken; else diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 212ae03dc1e..6b17cde4475 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -1490,14 +1490,15 @@ static QVariant fcnWordwrap( const QVariantList &values, const QgsExpressionCont static QVariant fcnLength( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { // two variants, one for geometry, one for string - if ( values.at( 0 ).userType() == qMetaTypeId< QgsGeometry>() ) - { - //geometry variant - QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent ); - if ( geom.type() != Qgis::GeometryType::Line ) - return QVariant(); - return QVariant( geom.length() ); + //geometry variant + QgsGeometry geom = QgsExpressionUtils::getGeometry( values.at( 0 ), parent, true ); + if ( !geom.isNull() ) + { + if ( geom.type() == Qgis::GeometryType::Line ) + return QVariant( geom.length() ); + else + return QVariant(); } //otherwise fall back to string variant @@ -3696,15 +3697,10 @@ static QVariant fcnCollectGeometries( const QVariantList &values, const QgsExpre parts.reserve( list.size() ); for ( const QVariant &value : std::as_const( list ) ) { - if ( value.userType() == qMetaTypeId< QgsGeometry>() ) - { - parts << value.value(); - } - else - { - parent->setEvalErrorString( QStringLiteral( "Cannot convert to geometry" ) ); + QgsGeometry part = QgsExpressionUtils::getGeometry( value, parent ); + if ( part.isNull() ) return QgsGeometry(); - } + parts << part; } return QgsGeometry::collectGeometry( parts ); diff --git a/src/core/expression/qgsexpressionutils.h b/src/core/expression/qgsexpressionutils.h index 4fbd3c0fa47..af4360cd9c3 100644 --- a/src/core/expression/qgsexpressionutils.h +++ b/src/core/expression/qgsexpressionutils.h @@ -23,6 +23,7 @@ #include "qgsexpression.h" #include "qgsvariantutils.h" #include "qgsfeaturerequest.h" +#include "qgsreferencedgeometry.h" #include #include @@ -94,15 +95,16 @@ class CORE_EXPORT QgsExpressionUtils return Unknown; //handle some special cases + int userType = value.userType(); if ( value.type() == QVariant::UserType ) { - if ( value.userType() == qMetaTypeId< QgsGeometry>() ) + if ( userType == qMetaTypeId< QgsGeometry>() || userType == qMetaTypeId() ) { //geom is false if empty - const QgsGeometry geom = value.value(); + const QgsGeometry geom = getGeometry( value, nullptr ); return geom.isNull() ? False : True; } - else if ( value.userType() == qMetaTypeId() ) + else if ( userType == qMetaTypeId() ) { //feat is false if non-valid const QgsFeature feat = value.value(); @@ -110,14 +112,15 @@ class CORE_EXPORT QgsExpressionUtils } } - if ( value.userType() == QMetaType::Type::Int ) + if ( userType == QMetaType::Type::Int ) return value.toInt() != 0 ? True : False; bool ok; const double x = value.toDouble( &ok ); if ( !ok ) { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to boolean" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to boolean" ).arg( value.toString() ) ); return Unknown; } return !qgsDoubleNear( x, 0.0 ) ? True : False; @@ -215,7 +218,8 @@ class CORE_EXPORT QgsExpressionUtils { if ( value.userType() != QMetaType::Type::QByteArray ) { - parent->setEvalErrorString( QObject::tr( "Value is not a binary value" ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Value is not a binary value" ) ); return QByteArray(); } return value.toByteArray(); @@ -227,7 +231,8 @@ class CORE_EXPORT QgsExpressionUtils const double x = value.toDouble( &ok ); if ( !ok || std::isnan( x ) || !std::isfinite( x ) ) { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to double" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to double" ).arg( value.toString() ) ); return 0; } return x; @@ -243,7 +248,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to int" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to int" ).arg( value.toString() ) ); return 0; } } @@ -258,7 +264,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to native int" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to native int" ).arg( value.toString() ) ); return 0; } } @@ -278,7 +285,8 @@ class CORE_EXPORT QgsExpressionUtils return QDateTime( QDate( 1, 1, 1 ), t ); } - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to DateTime" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to DateTime" ).arg( value.toString() ) ); return QDateTime(); } } @@ -292,7 +300,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Date" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Date" ).arg( value.toString() ) ); return QDate(); } } @@ -306,7 +315,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Time" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to Time" ).arg( value.toString() ) ); return QTime(); } } @@ -324,7 +334,7 @@ class CORE_EXPORT QgsExpressionUtils return inter; } // If we get here then we can't convert so we just error and return invalid. - if ( report_error ) + if ( report_error && parent ) parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to interval" ).arg( value.toString() ) ); return QgsInterval(); @@ -332,12 +342,16 @@ class CORE_EXPORT QgsExpressionUtils static QgsGradientColorRamp getRamp( const QVariant &value, QgsExpression *parent, bool report_error = false ); - static QgsGeometry getGeometry( const QVariant &value, QgsExpression *parent ) + static QgsGeometry getGeometry( const QVariant &value, QgsExpression *parent, bool tolerant = false ) { + if ( value.userType() == qMetaTypeId< QgsReferencedGeometry>() ) + return value.value(); + if ( value.userType() == qMetaTypeId< QgsGeometry>() ) return value.value(); - parent->setEvalErrorString( QStringLiteral( "Cannot convert to geometry" ) ); + if ( !tolerant && parent ) + parent->setEvalErrorString( QStringLiteral( "Cannot convert to geometry" ) ); return QgsGeometry(); } @@ -346,7 +360,8 @@ class CORE_EXPORT QgsExpressionUtils if ( value.userType() == qMetaTypeId() ) return value.value(); - parent->setEvalErrorString( QStringLiteral( "Cannot convert to feature" ) ); + if ( parent ) + parent->setEvalErrorString( QStringLiteral( "Cannot convert to feature" ) ); return 0; } @@ -355,7 +370,8 @@ class CORE_EXPORT QgsExpressionUtils if ( value.canConvert() ) return value.value(); - parent->setEvalErrorString( QStringLiteral( "Cannot convert to node" ) ); + if ( parent ) + parent->setEvalErrorString( QStringLiteral( "Cannot convert to node" ) ); return nullptr; } @@ -403,7 +419,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to array" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to array" ).arg( value.toString() ) ); return QVariantList(); } } @@ -416,7 +433,8 @@ class CORE_EXPORT QgsExpressionUtils } else { - parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to map" ).arg( value.toString() ) ); + if ( parent ) + parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to map" ).arg( value.toString() ) ); return QVariantMap(); } } diff --git a/src/core/geometry/qgsbox3d.cpp b/src/core/geometry/qgsbox3d.cpp index f16a1f004bc..abd9669d5d1 100644 --- a/src/core/geometry/qgsbox3d.cpp +++ b/src/core/geometry/qgsbox3d.cpp @@ -53,6 +53,17 @@ QgsBox3D::QgsBox3D( const QgsRectangle &rect, double zMin, double zMax, bool nor } } +QgsBox3D::QgsBox3D( const QgsVector3D &corner1, const QgsVector3D &corner2, bool normalize ) + : mBounds2d( corner1.x(), corner1.y(), corner2.x(), corner2.y(), false ) + , mZmin( corner1.z() ) + , mZmax( corner2.z() ) +{ + if ( normalize ) + { + QgsBox3D::normalize(); + } +} + void QgsBox3D::setXMinimum( double x ) { mBounds2d.setXMinimum( x ); @@ -286,6 +297,15 @@ void QgsBox3D::scale( double scaleFactor, double centerX, double centerY, double setZMaximum( centerZ + ( zMaximum() - centerZ ) * scaleFactor ); } +void QgsBox3D::grow( double delta ) +{ + if ( isNull() ) + return; + mBounds2d.grow( delta ); + mZmin -= delta; + mZmax += delta; +} + bool QgsBox3D::isNull() const { return ( std::isnan( mBounds2d.xMinimum() ) && std::isnan( mBounds2d.xMaximum() ) diff --git a/src/core/geometry/qgsbox3d.h b/src/core/geometry/qgsbox3d.h index 374e5719cbc..72dd81ae005 100644 --- a/src/core/geometry/qgsbox3d.h +++ b/src/core/geometry/qgsbox3d.h @@ -61,6 +61,14 @@ class CORE_EXPORT QgsBox3D */ QgsBox3D( const QgsPoint &p1, const QgsPoint &p2, bool normalize = true ); + /** + * Constructs a QgsBox3D from two 3D vectors representing opposite corners of the box. + * The box is normalized after construction. If \a normalize is FALSE then + * the normalization step will not be applied automatically. + * \since QGIS 3.42 + */ + QgsBox3D( const QgsVector3D &corner1, const QgsVector3D &corner2, bool normalize = true ); + /** * Constructs a QgsBox3D from a rectangle. * If \a normalize is FALSE then the normalization step will not be applied automatically. @@ -68,8 +76,9 @@ class CORE_EXPORT QgsBox3D QgsBox3D( const QgsRectangle &rect, double zMin = std::numeric_limits::quiet_NaN(), double zMax = std::numeric_limits::quiet_NaN(), bool normalize = true ); + #else - QgsBox3D( SIP_PYOBJECT x SIP_TYPEHINT( Optional[Union[QgsPoint, QgsRectangle, float]] ) = Py_None, SIP_PYOBJECT y SIP_TYPEHINT( Optional[QgsPoint, float] ) = Py_None, SIP_PYOBJECT z SIP_TYPEHINT( Optional[Union[bool, float]] ) = Py_None, SIP_PYOBJECT x2 SIP_TYPEHINT( Optional[Union[bool, float]] ) = Py_None, SIP_PYOBJECT y2 SIP_TYPEHINT( Optional[float] ) = Py_None, SIP_PYOBJECT z2 SIP_TYPEHINT( Optional[float] ) = Py_None, SIP_PYOBJECT n SIP_TYPEHINT( Optional[bool] ) = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double x2 = 0.0, double y2 = 0.0, double z2 = 0.0, bool n = true )]; + QgsBox3D( SIP_PYOBJECT x SIP_TYPEHINT( Optional[Union[QgsPoint, QgsVector3D, QgsRectangle, float]] ) = Py_None, SIP_PYOBJECT y SIP_TYPEHINT( Optional[QgsPoint, QgsVector3D, float] ) = Py_None, SIP_PYOBJECT z SIP_TYPEHINT( Optional[Union[bool, float]] ) = Py_None, SIP_PYOBJECT x2 SIP_TYPEHINT( Optional[Union[bool, float]] ) = Py_None, SIP_PYOBJECT y2 SIP_TYPEHINT( Optional[float] ) = Py_None, SIP_PYOBJECT z2 SIP_TYPEHINT( Optional[float] ) = Py_None, SIP_PYOBJECT n SIP_TYPEHINT( Optional[bool] ) = Py_None ) [( double x = 0.0, double y = 0.0, double z = 0.0, double x2 = 0.0, double y2 = 0.0, double z2 = 0.0, bool n = true )]; % MethodCode if ( sipCanConvertToType( a0, sipType_QgsRectangle, SIP_NOT_NONE ) && a4 == Py_None && a5 == Py_None && a6 == Py_None ) { @@ -114,6 +123,30 @@ class CORE_EXPORT QgsBox3D } } } + else if ( sipCanConvertToType( a0, sipType_QgsVector3D, SIP_NOT_NONE ) && sipCanConvertToType( a1, sipType_QgsVector3D, SIP_NOT_NONE ) && a3 == Py_None && a4 == Py_None && a5 == Py_None && a6 == Py_None ) + { + int state; + sipIsErr = 0; + + QgsVector3D *corner1 = reinterpret_cast( sipConvertToType( a0, sipType_QgsVector3D, 0, SIP_NOT_NONE, &state, &sipIsErr ) ); + if ( sipIsErr ) + { + sipReleaseType( corner1, sipType_QgsVector3D, state ); + } + else + { + QgsVector3D *corner2 = reinterpret_cast( sipConvertToType( a1, sipType_QgsVector3D, 0, SIP_NOT_NONE, &state, &sipIsErr ) ); + if ( sipIsErr ) + { + sipReleaseType( corner2, sipType_QgsVector3D, state ); + } + else + { + bool n = a2 == Py_None ? true : PyObject_IsTrue( a2 ); + sipCpp = new QgsBox3D( *corner1, *corner2, n ); + } + } + } else if ( ( a0 == Py_None || PyFloat_AsDouble( a0 ) != -1.0 || !PyErr_Occurred() ) && ( a1 == Py_None || PyFloat_AsDouble( a1 ) != -1.0 || !PyErr_Occurred() ) && @@ -386,6 +419,12 @@ class CORE_EXPORT QgsBox3D */ void scale( double scaleFactor, double centerX, double centerY, double centerZ ) SIP_HOLDGIL; + /** + * Grows the box in place by the specified amount in all dimensions. + * \since QGIS 3.42 + */ + void grow( double delta ); + /** * Test if the box is null (holding no spatial information). * diff --git a/src/core/geometry/qgscurve.cpp b/src/core/geometry/qgscurve.cpp index e256f914993..842877f10dd 100644 --- a/src/core/geometry/qgscurve.cpp +++ b/src/core/geometry/qgscurve.cpp @@ -253,7 +253,7 @@ bool QgsCurve::isValid( QString &error, Qgis::GeometryValidityFlags flags ) cons return error.isEmpty(); } - const QgsGeos geos( this ); + const QgsGeos geos( this, 0, Qgis::GeosCreationFlags() ); const bool res = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr ); if ( flags == 0 ) { diff --git a/src/core/geometry/qgsgeometry.cpp b/src/core/geometry/qgsgeometry.cpp index 768a4981ad6..ab0c310fba3 100644 --- a/src/core/geometry/qgsgeometry.cpp +++ b/src/core/geometry/qgsgeometry.cpp @@ -3483,7 +3483,7 @@ void QgsGeometry::validateGeometry( QVector &errors, const Q case Qgis::GeometryValidationEngine::Geos: { - QgsGeos geos( d->geometry.get() ); + QgsGeos geos( d->geometry.get(), 0, Qgis::GeosCreationFlags() ); QString error; QgsGeometry errorLoc; if ( !geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, &errorLoc ) ) @@ -4467,9 +4467,9 @@ QgsGeometry QgsGeometry::convertToPolygon( bool destMultipart ) const } } -QgsGeometryEngine *QgsGeometry::createGeometryEngine( const QgsAbstractGeometry *geometry, double precision ) +QgsGeometryEngine *QgsGeometry::createGeometryEngine( const QgsAbstractGeometry *geometry, double precision, Qgis::GeosCreationFlags flags ) { - return new QgsGeos( geometry, precision ); + return new QgsGeos( geometry, precision, flags ); } QDataStream &operator<<( QDataStream &out, const QgsGeometry &geometry ) diff --git a/src/core/geometry/qgsgeometry.h b/src/core/geometry/qgsgeometry.h index 4411e2a1c9a..c02ee5171f6 100644 --- a/src/core/geometry/qgsgeometry.h +++ b/src/core/geometry/qgsgeometry.h @@ -3137,6 +3137,8 @@ class CORE_EXPORT QgsGeometry * a large number of spatial relationships will be tested (such as calling intersects(), within(), etc) then the * geometry should first be prepared by calling prepareGeometry() before performing the tests. * + * The \a flags argument was added in QGIS 3.40 to allow control over the resultant GEOS geometry. + * * ### Example * * \code{.py} @@ -3160,7 +3162,7 @@ class CORE_EXPORT QgsGeometry * * QgsGeometryEngine operations are backed by the GEOS library (https://trac.osgeo.org/geos/). */ - static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0 ) SIP_FACTORY; + static QgsGeometryEngine *createGeometryEngine( const QgsAbstractGeometry *geometry, double precision = 0.0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ) SIP_FACTORY; /** * Upgrades a point list from QgsPointXY to QgsPoint diff --git a/src/core/geometry/qgsgeos.h b/src/core/geometry/qgsgeos.h index 4adec18cc48..454ae0c44bc 100644 --- a/src/core/geometry/qgsgeos.h +++ b/src/core/geometry/qgsgeos.h @@ -144,7 +144,7 @@ class CORE_EXPORT QgsGeos: public QgsGeometryEngine * \param flags GEOS creation flags (since QGIS 3.40) * \note The third parameter was prior to QGIS 3.40 a boolean which has been incorporated into the flag */ - QgsGeos( const QgsAbstractGeometry *geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlags() ); + QgsGeos( const QgsAbstractGeometry *geometry, double precision = 0, Qgis::GeosCreationFlags flags = Qgis::GeosCreationFlag::SkipEmptyInteriorRings ); /** * Creates a new QgsGeometry object, feeding in a geometry in GEOS format. diff --git a/src/core/geometry/qgslinestring.cpp b/src/core/geometry/qgslinestring.cpp index 419d9cd20ad..a0e473970a7 100644 --- a/src/core/geometry/qgslinestring.cpp +++ b/src/core/geometry/qgslinestring.cpp @@ -1086,6 +1086,40 @@ std::tuple, std::unique_ptr > QgsLineString: return std::make_tuple( std::make_unique< QgsLineString >( x1, y1, z1, m1 ), std::make_unique< QgsLineString >( x2, y2, z2, m2 ) ); } +QVector QgsLineString::splitToDisjointXYParts() const +{ + const double *allPointsX = xData(); + const double *allPointsY = yData(); + size_t allPointsCount = numPoints(); + QVector partX; + QVector partY; + QSet partPointSet; + + QVector disjointParts; + for ( size_t i = 0; i < allPointsCount; i++ ) + { + const QgsPointXY point( *allPointsX++, *allPointsY++ ); + if ( partPointSet.contains( point ) ) + { + // This point is used multiple times, cut the curve and add the + // current part + disjointParts.push_back( new QgsLineString( partX, partY ) ); + // Now start a new part containing the last line + partX = { partX.last() }; + partY = { partY.last() }; + partPointSet = { QgsPointXY( partX[0], partY[0] ) }; + } + partX.push_back( point.x() ); + partY.push_back( point.y() ); + partPointSet.insert( point ); + } + // Add the last part (if we didn't stop by closing the loop) + if ( partX.size() > 1 || disjointParts.size() == 0 ) + disjointParts.push_back( new QgsLineString( partX, partY ) ); + + return disjointParts; +} + double QgsLineString::length3D() const { if ( is3D() ) diff --git a/src/core/geometry/qgslinestring.h b/src/core/geometry/qgslinestring.h index deca218b11f..6d02dca9874 100644 --- a/src/core/geometry/qgslinestring.h +++ b/src/core/geometry/qgslinestring.h @@ -991,6 +991,16 @@ class CORE_EXPORT QgsLineString: public QgsCurve std::tuple< std::unique_ptr< QgsCurve >, std::unique_ptr< QgsCurve > > splitCurveAtVertex( int index ) const final; #endif + /** + * Divides the linestring into parts that don't share any points or lines. + * + * This method throws away Z and M coordinates. + * + * The ownership of returned pointers is transferred to the caller. + * \since QGIS 3.40 + */ + QVector splitToDisjointXYParts() const SIP_FACTORY; + /** * Returns the length in 3D world of the line string. * If it is not a 3D line string, return its 2D length. diff --git a/src/core/geometry/qgspolyhedralsurface.cpp b/src/core/geometry/qgspolyhedralsurface.cpp index cf2b7b09540..ce418562e4a 100644 --- a/src/core/geometry/qgspolyhedralsurface.cpp +++ b/src/core/geometry/qgspolyhedralsurface.cpp @@ -1013,7 +1013,7 @@ bool QgsPolyhedralSurface::isValid( QString &error, Qgis::GeometryValidityFlags // GEOS does not handle PolyhedralSurface, check the polygons one by one for ( int i = 0; i < mPatches.size(); ++i ) { - const QgsGeos geos( mPatches.at( i ) ); + const QgsGeos geos( mPatches.at( i ), 0, Qgis::GeosCreationFlags() ); const bool valid = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr ); if ( !valid ) { diff --git a/src/core/geometry/qgssurface.cpp b/src/core/geometry/qgssurface.cpp index 44045c0bcd3..0f91df02a92 100644 --- a/src/core/geometry/qgssurface.cpp +++ b/src/core/geometry/qgssurface.cpp @@ -30,7 +30,7 @@ bool QgsSurface::isValid( QString &error, Qgis::GeometryValidityFlags flags ) co return error.isEmpty(); } - const QgsGeos geos( this ); + const QgsGeos geos( this, 0, Qgis::GeosCreationFlags() ); const bool res = geos.isValid( &error, flags & Qgis::GeometryValidityFlag::AllowSelfTouchingHoles, nullptr ); if ( flags == 0 ) { diff --git a/src/core/layout/qgslayoutexporter.cpp b/src/core/layout/qgslayoutexporter.cpp index 6ae555dbb0a..f364465510a 100644 --- a/src/core/layout/qgslayoutexporter.cpp +++ b/src/core/layout/qgslayoutexporter.cpp @@ -155,6 +155,7 @@ class LayoutItemHider const QgsSettingsEntryBool *QgsLayoutExporter::settingOpenAfterExportingImage = new QgsSettingsEntryBool( QStringLiteral( "open-after-exporting-image" ), QgsSettingsTree::sTreeLayout, false, QObject::tr( "Whether to open the exported image file with the default viewer after exporting a print layout" ) ); const QgsSettingsEntryBool *QgsLayoutExporter::settingOpenAfterExportingPdf = new QgsSettingsEntryBool( QStringLiteral( "open-after-exporting-pdf" ), QgsSettingsTree::sTreeLayout, false, QObject::tr( "Whether to open the exported PDF file with the default viewer after exporting a print layout" ) ); const QgsSettingsEntryBool *QgsLayoutExporter::settingOpenAfterExportingSvg = new QgsSettingsEntryBool( QStringLiteral( "open-after-exporting-svg" ), QgsSettingsTree::sTreeLayout, false, QObject::tr( "Whether to open the exported SVG file with the default viewer after exporting a print layout" ) ); +const QgsSettingsEntryInteger *QgsLayoutExporter::settingImageQuality = new QgsSettingsEntryInteger( QStringLiteral( "image-quality" ), QgsSettingsTree::sTreeLayout, 90, QObject::tr( "Image quality for lossy formats (e.g. JPEG)" ) ); QgsLayoutExporter::QgsLayoutExporter( QgsLayout *layout ) : mLayout( layout ) @@ -456,7 +457,7 @@ QgsLayoutExporter::ExportResult QgsLayoutExporter::exportToImage( const QString return MemoryError; } - if ( !saveImage( image, outputFilePath, pageDetails.extension, settings.exportMetadata ? mLayout->project() : nullptr ) ) + if ( !saveImage( image, outputFilePath, pageDetails.extension, settings.exportMetadata ? mLayout->project() : nullptr, settings.quality ) ) { mErrorFileName = outputFilePath; return FileError; @@ -2216,13 +2217,17 @@ void QgsLayoutExporter::captureLabelingResults() } } -bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata ) +bool QgsLayoutExporter::saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata, int quality ) { QImageWriter w( imageFilename, imageFormat.toLocal8Bit().constData() ); if ( imageFormat.compare( QLatin1String( "tiff" ), Qt::CaseInsensitive ) == 0 || imageFormat.compare( QLatin1String( "tif" ), Qt::CaseInsensitive ) == 0 ) { w.setCompression( 1 ); //use LZW compression } + + // Set the quality for i.e. JPEG images. -1 means default quality. + w.setQuality( quality ); + if ( projectForMetadata ) { w.setText( QStringLiteral( "Author" ), projectForMetadata->metadata().author() ); diff --git a/src/core/layout/qgslayoutexporter.h b/src/core/layout/qgslayoutexporter.h index e15f6b6692c..2557c990ba4 100644 --- a/src/core/layout/qgslayoutexporter.h +++ b/src/core/layout/qgslayoutexporter.h @@ -41,6 +41,7 @@ class QgsAbstractLayoutIterator; class QgsFeedback; class QgsLabelingResults; class QgsSettingsEntryBool; +class QgsSettingsEntryInteger; /** * \ingroup core @@ -61,6 +62,9 @@ class CORE_EXPORT QgsLayoutExporter //! Settings entry - Whether to automatically open svgs after exporting them \since QGIS 3.34 static const QgsSettingsEntryBool *settingOpenAfterExportingSvg SIP_SKIP; + //! Settings entry - Image quality for lossy formats \since QGIS 3.42 + static const QgsSettingsEntryInteger *settingImageQuality SIP_SKIP; + //! Contains details of a page being exported by the class struct PageExportDetails { @@ -231,6 +235,14 @@ class CORE_EXPORT QgsLayoutExporter */ QVector predefinedMapScales; + + /** + * Image quality, typically used for JPEG compression (whose quality ranges from 1 to 100) + * if quality is set to -1, the default quality will be used. + * \since QGIS 3.42 + */ + int quality = -1; + }; /** @@ -719,7 +731,7 @@ class CORE_EXPORT QgsLayoutExporter /** * Saves an image to a file, possibly using format specific options (e.g. LZW compression for tiff) */ - static bool saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata ); + static bool saveImage( const QImage &image, const QString &imageFilename, const QString &imageFormat, QgsProject *projectForMetadata, int quality = -1 ); /** * Computes a GDAL style geotransform for georeferencing a layout. diff --git a/src/core/layout/qgslayoutitemnodeitem.h b/src/core/layout/qgslayoutitemnodeitem.h index 89145c413de..2b18a3f1499 100644 --- a/src/core/layout/qgslayoutitemnodeitem.h +++ b/src/core/layout/qgslayoutitemnodeitem.h @@ -113,6 +113,15 @@ class CORE_EXPORT QgsLayoutNodesItem: public QgsLayoutItem // rather than the item's pen double estimatedFrameBleed() const override; + /** + * Must be reimplemented in subclasses. + * Typically a polyline is valid if it has at least 2 distinct nodes, + * while a polygon is valid if it has at least 3 distinct nodes. + * + * \since QGIS 3.40 + */ + virtual bool isValid() const = 0; + protected: /** diff --git a/src/core/layout/qgslayoutitempolygon.cpp b/src/core/layout/qgslayoutitempolygon.cpp index e75cec3a1a6..0f9966d83cb 100644 --- a/src/core/layout/qgslayoutitempolygon.cpp +++ b/src/core/layout/qgslayoutitempolygon.cpp @@ -129,6 +129,24 @@ QgsGeometry QgsLayoutItemPolygon::clipPath() const return QgsGeometry::fromQPolygonF( path ); } + +bool QgsLayoutItemPolygon::isValid() const +{ + // A Polygon is valid if it has at least 3 unique points + QList uniquePoints; + int seen = 0; + for ( QPointF point : mPolygon ) + { + if ( !uniquePoints.contains( point ) ) + { + uniquePoints.append( point ); + if ( ++seen > 2 ) + return true; + } + } + return false; +} + QgsFillSymbol *QgsLayoutItemPolygon::symbol() { return mPolygonStyleSymbol.get(); @@ -177,20 +195,15 @@ void QgsLayoutItemPolygon::_writeXmlStyle( QDomDocument &doc, QDomElement &elmt, bool QgsLayoutItemPolygon::_removeNode( const int index ) { - if ( index < 0 || index >= mPolygon.size() ) + if ( index < 0 || index >= mPolygon.size() || mPolygon.size() <= 3 ) return false; mPolygon.remove( index ); - if ( mPolygon.size() < 3 ) - mPolygon.clear(); - else - { - int newSelectNode = index; - if ( index == mPolygon.size() ) - newSelectNode = 0; - setSelectedNode( newSelectNode ); - } + int newSelectNode = index; + if ( index == mPolygon.size() ) + newSelectNode = 0; + setSelectedNode( newSelectNode ); return true; } diff --git a/src/core/layout/qgslayoutitempolygon.h b/src/core/layout/qgslayoutitempolygon.h index 4384ce0cc3f..478855d44bf 100644 --- a/src/core/layout/qgslayoutitempolygon.h +++ b/src/core/layout/qgslayoutitempolygon.h @@ -58,6 +58,7 @@ class CORE_EXPORT QgsLayoutItemPolygon: public QgsLayoutNodesItem bool accept( QgsStyleEntityVisitorInterface *visitor ) const override; QgsLayoutItem::Flags itemFlags() const override; QgsGeometry clipPath() const override; + bool isValid() const override; /** * Returns the fill symbol used to draw the shape. diff --git a/src/core/layout/qgslayoutitempolyline.cpp b/src/core/layout/qgslayoutitempolyline.cpp index 8702eaa665a..78f07d3b922 100644 --- a/src/core/layout/qgslayoutitempolyline.cpp +++ b/src/core/layout/qgslayoutitempolyline.cpp @@ -83,20 +83,15 @@ bool QgsLayoutItemPolyline::_addNode( const int indexPoint, bool QgsLayoutItemPolyline::_removeNode( const int index ) { - if ( index < 0 || index >= mPolygon.size() ) + if ( index < 0 || index >= mPolygon.size() || mPolygon.size() <= 2 ) return false; mPolygon.remove( index ); - if ( mPolygon.size() < 2 ) - mPolygon.clear(); - else - { - int newSelectNode = index; - if ( index >= mPolygon.size() ) - newSelectNode = mPolygon.size() - 1; - setSelectedNode( newSelectNode ); - } + int newSelectNode = index; + if ( index >= mPolygon.size() ) + newSelectNode = mPolygon.size() - 1; + setSelectedNode( newSelectNode ); return true; } @@ -344,6 +339,23 @@ QPainterPath QgsLayoutItemPolyline::shape() const return strokedOutline; } +bool QgsLayoutItemPolyline::isValid() const +{ + // A Polyline is valid if it has at least 2 unique points + QList uniquePoints; + int seen = 0; + for ( QPointF point : mPolygon ) + { + if ( !uniquePoints.contains( point ) ) + { + uniquePoints.append( point ); + if ( ++seen > 1 ) + return true; + } + } + return false; +} + QgsLineSymbol *QgsLayoutItemPolyline::symbol() { return mPolylineStyleSymbol.get(); diff --git a/src/core/layout/qgslayoutitempolyline.h b/src/core/layout/qgslayoutitempolyline.h index 5dab88b9bea..4e1a7d952ea 100644 --- a/src/core/layout/qgslayoutitempolyline.h +++ b/src/core/layout/qgslayoutitempolyline.h @@ -66,6 +66,7 @@ class CORE_EXPORT QgsLayoutItemPolyline: public QgsLayoutNodesItem QIcon icon() const override; QString displayName() const override; QPainterPath shape() const override; + bool isValid() const override; /** * Returns the line symbol used to draw the shape. diff --git a/src/core/layout/qgslayoutpagecollection.cpp b/src/core/layout/qgslayoutpagecollection.cpp index 39140b90733..c49db2e126a 100644 --- a/src/core/layout/qgslayoutpagecollection.cpp +++ b/src/core/layout/qgslayoutpagecollection.cpp @@ -444,6 +444,32 @@ const QgsLayoutGuideCollection &QgsLayoutPageCollection::guides() const return *mGuideCollection; } +void QgsLayoutPageCollection::applyPropertiesToAllOtherPages( int sourcePage ) +{ + QgsLayoutItemPage *referencePage = page( sourcePage ); + if ( !referencePage ) + { + return; + } + + mLayout->undoStack()->beginCommand( this, tr( "Apply page properties" ) ); + mBlockUndoCommands = true; + + for ( QgsLayoutItemPage *page : mPages ) + { + if ( page == referencePage ) + { + continue; + } + page->setPageSize( referencePage->pageSize() ); + page->setPageStyleSymbol( referencePage->pageStyleSymbol()->clone() ); + } + + mLayout->undoStack()->endCommand(); + mBlockUndoCommands = false; + mLayout->refresh(); +} + void QgsLayoutPageCollection::redraw() { const auto constMPages = mPages; diff --git a/src/core/layout/qgslayoutpagecollection.h b/src/core/layout/qgslayoutpagecollection.h index 6a4406347e2..e11640746a3 100644 --- a/src/core/layout/qgslayoutpagecollection.h +++ b/src/core/layout/qgslayoutpagecollection.h @@ -388,6 +388,13 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject, public QgsLayoutSeri */ SIP_SKIP const QgsLayoutGuideCollection &guides() const; + /** + * Apply the source page properties (size & background color) to all other pages + * + * \since QGIS 3.42 + */ + void applyPropertiesToAllOtherPages( int sourcePage ); + public slots: /** diff --git a/src/core/pointcloud/qgslazdecoder.cpp b/src/core/pointcloud/qgslazdecoder.cpp index bfb10997a37..f78dc5cdf0b 100644 --- a/src/core/pointcloud/qgslazdecoder.cpp +++ b/src/core/pointcloud/qgslazdecoder.cpp @@ -307,7 +307,7 @@ std::vector< QgsLazDecoder::RequestedAttributeDetails > prepareRequestedAttribut return requestedAttributeDetails; } -void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t &outputOffset, std::vector< QgsLazDecoder::RequestedAttributeDetails > &requestedAttributeDetails ) +bool decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t &outputOffset, std::vector< QgsLazDecoder::RequestedAttributeDetails > &requestedAttributeDetails ) { lazperf::las::point10 p10; lazperf::las::gpstime gps; @@ -355,6 +355,7 @@ void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t & default: Q_ASSERT( false ); // must not happen - we checked earlier that the format is supported + return false; } for ( const QgsLazDecoder::RequestedAttributeDetails &requestedAttribute : requestedAttributeDetails ) @@ -427,7 +428,7 @@ void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t & lazStoreToStream_( dataBuffer, outputOffset, requestedAttribute.type, rgb.b ); break; case QgsLazDecoder::LazAttribute::ScannerChannel: - lazStoreToStream_( dataBuffer, outputOffset, requestedAttribute.type, char( p14.scannerChannel() ) ); + lazStoreToStream_( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( p14.scannerChannel() ) : 0 ); break; case QgsLazDecoder::LazAttribute::Synthetic: lazStoreToStream_( dataBuffer, outputOffset, requestedAttribute.type, isLas14 ? char( ( p14.classFlags() >> 0 ) & 0x01 ) : char( ( p10.classification >> 5 ) & 0x01 ) ); @@ -510,6 +511,7 @@ void decodePoint( char *buf, int lasPointFormat, char *dataBuffer, std::size_t & outputOffset += requestedAttribute.size; } + return true; } template @@ -592,11 +594,10 @@ std::unique_ptr decompressLaz_( FileType &file, const QgsPoi { f.readPoint( buf ); // read the point out - decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails ); + bool skipThisPoint = !decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails ); // check if point needs to be filtered out - bool skipThisPoint = false; - if ( hasFilterRect && attributeX && attributeY ) + if ( !skipThisPoint && hasFilterRect && attributeX && attributeY ) { const double x = attributeX->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + xAttributeOffset ); const double y = attributeY->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + yAttributeOffset ); @@ -703,12 +704,10 @@ std::unique_ptr QgsLazDecoder::decompressCopc( const QByteAr decompressor.decompress( decodedData.get() ); char *buf = decodedData.get(); - decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails ); + bool skipThisPoint = !decodePoint( buf, lasPointFormat, dataBuffer, outputOffset, requestedAttributeDetails ); // check if point needs to be filtered out - bool skipThisPoint = false; - - if ( hasFilterRect && attributeX && attributeY ) + if ( !skipThisPoint && hasFilterRect && attributeX && attributeY ) { const double x = attributeX->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + xAttributeOffset ); const double y = attributeY->convertValueToDouble( dataBuffer + outputOffset - requestedPointRecordSize + yAttributeOffset ); diff --git a/src/core/pointcloud/qgspointclouddataprovider.cpp b/src/core/pointcloud/qgspointclouddataprovider.cpp index 63de643c82f..994935d2b0d 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.cpp +++ b/src/core/pointcloud/qgspointclouddataprovider.cpp @@ -311,13 +311,36 @@ QVector QgsPointCloudDataProvider::identify( double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange, int pointsLimit ) +{ + QVector acceptedPoints; + + // Try sub-indexes first + for ( QgsPointCloudSubIndex &subidx : subIndexes() ) + { + // Check if the sub-index is relevant and if it is loaded. We shouldn't + // need to identify points in unloaded indices. + if ( !subidx.index() + || ( !subidx.zRange().overlaps( extentZRange ) ) + || !subidx.polygonBounds().intersects( extentGeometry ) ) + continue; + acceptedPoints.append( identify( subidx.index(), maxError, extentGeometry, extentZRange, pointsLimit ) ); + } + + // Then look at main index + acceptedPoints.append( identify( index(), maxError, extentGeometry, extentZRange, pointsLimit ) ); + + return acceptedPoints; +} + +QVector QgsPointCloudDataProvider::identify( + QgsPointCloudIndex *index, double maxError, + const QgsGeometry &extentGeometry, + const QgsDoubleRange &extentZRange, int pointsLimit ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS QVector acceptedPoints; - QgsPointCloudIndex *index = this->index(); - if ( !index || !index->isValid() ) return acceptedPoints; diff --git a/src/core/pointcloud/qgspointclouddataprovider.h b/src/core/pointcloud/qgspointclouddataprovider.h index 158d4736461..94fdf6f6ca0 100644 --- a/src/core/pointcloud/qgspointclouddataprovider.h +++ b/src/core/pointcloud/qgspointclouddataprovider.h @@ -385,6 +385,9 @@ class CORE_EXPORT QgsPointCloudDataProvider: public QgsDataProvider //! String used to define a subset of the layer QString mSubsetString; + //! Identify in a specific index (used for sub-indexes) + QVector identify( QgsPointCloudIndex *index, double maxError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange, int pointsLimit ) SIP_SKIP ; + private: QVector traverseTree( const QgsPointCloudIndex *pc, IndexedPointCloudNode n, double maxError, double nodeError, const QgsGeometry &extentGeometry, const QgsDoubleRange &extentZRange ); diff --git a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp index 420a3eb6e91..9982c4f1d32 100644 --- a/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp +++ b/src/core/pointcloud/qgspointcloudlayerprofilegenerator.cpp @@ -389,12 +389,23 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera // this is not AT ALL thread safe, but it's what QgsPointCloudLayerRenderer does ! // TODO: fix when QgsPointCloudLayerRenderer is made thread safe to use same approach - QgsPointCloudIndex *pc = mLayer->dataProvider()->index(); - if ( !pc || !pc->isValid() ) + QVector indexes; + QgsPointCloudIndex *mainIndex = mLayer->dataProvider()->index(); + if ( mainIndex && mainIndex->isValid() ) + indexes.append( mainIndex ); + + // Gather all relevant sub-indexes + const QgsRectangle profileCurveBbox = mProfileCurve->boundingBox(); + for ( const QgsPointCloudSubIndex &subidx : mLayer->dataProvider()->subIndexes() ) { - return false; + QgsPointCloudIndex *index = subidx.index(); + if ( index && index->isValid() && subidx.polygonBounds().intersects( profileCurveBbox ) ) + indexes.append( subidx.index() ); } + if ( indexes.empty() ) + return false; + const double startDistanceOffset = std::max( !context.distanceRange().isInfinite() ? context.distanceRange().lower() : 0, 0.0 ); const double endDistance = context.distanceRange().upper(); @@ -433,9 +444,13 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera mSearchGeometryInLayerCrsGeometryEngine->prepareGeometry(); mMaxSearchExtentInLayerCrs = mSearchGeometryInLayerCrs->boundingBox(); - const IndexedPointCloudNode root = pc->root(); - double maximumErrorPixels = context.convertDistanceToPixels( mMaximumScreenError, mMaximumScreenErrorUnit ); + if ( maximumErrorPixels < 0.0 ) + { + QgsDebugError( QStringLiteral( "Invalid maximum error in pixels" ) ); + return false; + } + const double toleranceInPixels = context.convertDistanceToPixels( mTolerance, Qgis::RenderUnit::MapUnits ); // ensure that the maximum error is compatible with the tolerance size -- otherwise if the tolerance size // is much smaller than the maximum error, we don't dig deep enough into the point cloud nodes to find @@ -444,42 +459,6 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera if ( toleranceInPixels / 4 < maximumErrorPixels ) maximumErrorPixels = toleranceInPixels / 4; - const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root ); - QgsRectangle rootNodeExtentInCurveCrs; - try - { - QgsCoordinateTransform extentTransform = mLayerToTargetTransform; - extentTransform.setBallparkTransformsAreAppropriate( true ); - rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords ); - } - catch ( QgsCsException & ) - { - QgsDebugError( QStringLiteral( "Could not transform node extent to curve CRS" ) ); - rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords; - } - - const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc->span(); // in curve coords - - const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel(); - if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maximumErrorPixels < 0.0 ) ) - { - QgsDebugError( QStringLiteral( "invalid screen error" ) ); - return false; - } - double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels - const QVector nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() ); - if ( nodes.empty() ) - { - return false; - } - - const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc->span(); - const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel; - - mResults = std::make_unique< QgsPointCloudLayerProfileResults >(); - mResults->copyPropertiesFromGenerator( this ); - mResults->mMaxErrorInLayerCoordinates = maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates; - QgsPointCloudRequest request; QgsPointCloudAttributeCollection attributes; attributes.push_back( QgsPointCloudAttribute( QStringLiteral( "X" ), QgsPointCloudAttribute::Int32 ) ); @@ -515,22 +494,75 @@ bool QgsPointCloudLayerProfileGenerator::generateProfile( const QgsProfileGenera request.setAttributes( attributes ); - switch ( pc->accessType() ) + mResults = std::make_unique< QgsPointCloudLayerProfileResults >(); + mResults->copyPropertiesFromGenerator( this ); + mResults->mMaxErrorInLayerCoordinates = 0; + + QgsCoordinateTransform extentTransform = mLayerToTargetTransform; + extentTransform.setBallparkTransformsAreAppropriate( true ); + + const double mapUnitsPerPixel = context.mapUnitsPerDistancePixel(); + if ( mapUnitsPerPixel < 0.0 ) { - case QgsPointCloudIndex::AccessType::Local: - { - visitNodesSync( nodes, pc, request, context.elevationRange() ); - break; - } - case QgsPointCloudIndex::AccessType::Remote: - { - visitNodesAsync( nodes, pc, request, context.elevationRange() ); - break; - } + QgsDebugError( QStringLiteral( "Invalid map units per pixel ratio" ) ); + return false; } - if ( mFeedback->isCanceled() ) + for ( QgsPointCloudIndex *pc : std::as_const( indexes ) ) + { + const IndexedPointCloudNode root = pc->root(); + const QgsRectangle rootNodeExtentLayerCoords = pc->nodeMapExtent( root ); + QgsRectangle rootNodeExtentInCurveCrs; + try + { + rootNodeExtentInCurveCrs = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords ); + } + catch ( QgsCsException & ) + { + QgsDebugError( QStringLiteral( "Could not transform node extent to curve CRS" ) ); + rootNodeExtentInCurveCrs = rootNodeExtentLayerCoords; + } + + const double rootErrorInMapCoordinates = rootNodeExtentInCurveCrs.width() / pc->span(); // in curve coords + if ( rootErrorInMapCoordinates < 0.0 ) + { + QgsDebugError( QStringLiteral( "Invalid root node error" ) ); + return false; + } + + double rootErrorPixels = rootErrorInMapCoordinates / mapUnitsPerPixel; // in pixels + const QVector nodes = traverseTree( pc, pc->root(), maximumErrorPixels, rootErrorPixels, context.elevationRange() ); + + const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / pc->span(); + const double maxErrorInMapCoordinates = maximumErrorPixels * mapUnitsPerPixel; + + mResults->mMaxErrorInLayerCoordinates = std::max( + mResults->mMaxErrorInLayerCoordinates, + maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates ); + + switch ( pc->accessType() ) + { + case QgsPointCloudIndex::AccessType::Local: + { + visitNodesSync( nodes, pc, request, context.elevationRange() ); + break; + } + case QgsPointCloudIndex::AccessType::Remote: + { + visitNodesAsync( nodes, pc, request, context.elevationRange() ); + break; + } + } + + if ( mFeedback->isCanceled() ) + return false; + } + + if ( mGatheredPoints.empty() ) + { + mResults = nullptr; return false; + } // convert x/y values back to distance/height values diff --git a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp index 276167147f5..f127228aaa2 100644 --- a/src/core/pointcloud/qgspointcloudlayerrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudlayerrenderer.cpp @@ -710,7 +710,7 @@ void QgsPointCloudLayerRenderer::renderTriangulatedSurface( QgsPointCloudRenderC QPainter *painter = context.renderContext().painter(); QgsElevationMap *elevationMap = context.renderContext().elevationMap(); QPointF triangle[3]; - float elev[3]; + float elev[3] {0, 0, 0}; for ( size_t i = 0; i < triangleIndexes.size(); i += 3 ) { size_t v0 = triangleIndexes[i], v1 = triangleIndexes[i + 1], v2 = triangleIndexes[i + 2]; diff --git a/src/core/pointcloud/qgspointcloudrenderer.cpp b/src/core/pointcloud/qgspointcloudrenderer.cpp index 4815f2387c4..83204c24200 100644 --- a/src/core/pointcloud/qgspointcloudrenderer.cpp +++ b/src/core/pointcloud/qgspointcloudrenderer.cpp @@ -265,48 +265,33 @@ QVector QgsPointCloudRenderer::identify( QgsPointCloudLayer *layer, { QVector selectedPoints; - QgsPointCloudIndex *index = layer->dataProvider()->index(); - - if ( !index || !index->isValid() ) - return selectedPoints; - - const IndexedPointCloudNode root = index->root(); - const double maxErrorPixels = renderContext.convertToPainterUnits( maximumScreenError(), maximumScreenErrorUnit() );// in pixels - const QgsRectangle rootNodeExtentLayerCoords = index->nodeMapExtent( root ); - QgsRectangle rootNodeExtentMapCoords; + const QgsRectangle layerExtentLayerCoords = layer->dataProvider()->extent(); + QgsRectangle layerExtentMapCoords = layerExtentLayerCoords; if ( !renderContext.coordinateTransform().isShortCircuited() ) { try { QgsCoordinateTransform extentTransform = renderContext.coordinateTransform(); extentTransform.setBallparkTransformsAreAppropriate( true ); - rootNodeExtentMapCoords = extentTransform.transformBoundingBox( rootNodeExtentLayerCoords ); + layerExtentMapCoords = extentTransform.transformBoundingBox( layerExtentLayerCoords ); } catch ( QgsCsException & ) { QgsDebugError( QStringLiteral( "Could not transform node extent to map CRS" ) ); - rootNodeExtentMapCoords = rootNodeExtentLayerCoords; } } - else - { - rootNodeExtentMapCoords = rootNodeExtentLayerCoords; - } - - const double rootErrorInMapCoordinates = rootNodeExtentMapCoords.width() / index->span(); - const double rootErrorInLayerCoordinates = rootNodeExtentLayerCoords.width() / index->span(); const double mapUnitsPerPixel = renderContext.mapToPixel().mapUnitsPerPixel(); - if ( ( rootErrorInMapCoordinates < 0.0 ) || ( mapUnitsPerPixel < 0.0 ) || ( maxErrorPixels < 0.0 ) ) + if ( ( mapUnitsPerPixel < 0.0 ) || ( maxErrorPixels < 0.0 ) ) { QgsDebugError( QStringLiteral( "invalid screen error" ) ); return selectedPoints; } const double maxErrorInMapCoordinates = maxErrorPixels * mapUnitsPerPixel; - const double maxErrorInLayerCoordinates = maxErrorInMapCoordinates * rootErrorInLayerCoordinates / rootErrorInMapCoordinates; + const double maxErrorInLayerCoordinates = maxErrorInMapCoordinates * layerExtentLayerCoords.width() / layerExtentMapCoords.width(); QgsGeometry selectionGeometry = geometry; if ( geometry.type() == Qgis::GeometryType::Point ) diff --git a/src/core/qgsgdalutils.cpp b/src/core/qgsgdalutils.cpp index cd3e29251db..afe05bf0657 100644 --- a/src/core/qgsgdalutils.cpp +++ b/src/core/qgsgdalutils.cpp @@ -855,7 +855,13 @@ QString QgsGdalUtils::vsiPrefixForPath( const QString &path ) return QStringLiteral( "/vsizip/" ); } else if ( path.endsWith( QLatin1String( ".zip" ), Qt::CaseInsensitive ) ) + { + // GTFS driver directly handles .zip files + const char *const apszAllowedDrivers[] = { "GTFS", nullptr }; + if ( GDALIdentifyDriverEx( path.toUtf8().constData(), GDAL_OF_VECTOR, apszAllowedDrivers, nullptr ) ) + return QString(); return QStringLiteral( "/vsizip/" ); + } else if ( path.endsWith( QLatin1String( ".tar" ), Qt::CaseInsensitive ) || path.endsWith( QLatin1String( ".tar.gz" ), Qt::CaseInsensitive ) || path.endsWith( QLatin1String( ".tgz" ), Qt::CaseInsensitive ) ) diff --git a/src/core/qgsgeometryvalidator.cpp b/src/core/qgsgeometryvalidator.cpp index 66f147f2ec7..92c88cffc43 100644 --- a/src/core/qgsgeometryvalidator.cpp +++ b/src/core/qgsgeometryvalidator.cpp @@ -257,7 +257,7 @@ void QgsGeometryValidator::run() return; } - const QgsGeos geos( mGeometry.constGet() ); + const QgsGeos geos( mGeometry.constGet(), 0, Qgis::GeosCreationFlags() ); QString error; QgsGeometry errorLoc; if ( !geos.isValid( &error, true, &errorLoc ) ) diff --git a/src/core/qgsmultirenderchecker.cpp b/src/core/qgsmultirenderchecker.cpp index 7dbab9950cf..b446bc90ec6 100644 --- a/src/core/qgsmultirenderchecker.cpp +++ b/src/core/qgsmultirenderchecker.cpp @@ -78,13 +78,11 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma // we can only report one diff image, so just use the first QString diffImageFile; + QMap< QString, int > variantMismatchCount; + QMap< QString, int > variantSize; + for ( const QString &suffix : std::as_const( subDirs ) ) { - if ( subDirs.count() > 1 ) - { - qDebug() << "Checking subdir " << suffix; - } - bool result; QgsRenderChecker checker; checker.enableDashBuffering( true ); checker.setColorTolerance( mColorTolerance ); @@ -95,14 +93,15 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma checker.setMapSettings( mMapSettings ); checker.setExpectFail( mExpectFail ); + bool result = false; if ( !mRenderedImage.isNull() ) { checker.setRenderedImage( mRenderedImage ); - result = checker.compareImages( testName, mismatchCount, mRenderedImage, QgsRenderChecker::Flag::AvoidExportingRenderedImage ); + result = checker.compareImages( testName, mismatchCount, mRenderedImage, QgsRenderChecker::Flag::AvoidExportingRenderedImage | QgsRenderChecker::Flag::Silent ); } else { - result = checker.runTest( testName, mismatchCount, QgsRenderChecker::Flag::AvoidExportingRenderedImage ); + result = checker.runTest( testName, mismatchCount, QgsRenderChecker::Flag::AvoidExportingRenderedImage | QgsRenderChecker::Flag::Silent ); mRenderedImage = checker.renderedImage(); } @@ -120,6 +119,11 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma { diffImageFile = checker.mDiffImageFile; } + if ( !mResult ) + { + variantMismatchCount.insert( suffix, checker.mismatchCount() ); + variantSize.insert( suffix, checker.matchTarget() ); + } } if ( !mResult && !mExpectFail && mIsCiRun ) @@ -148,6 +152,17 @@ bool QgsMultiRenderChecker::runTest( const QString &testName, unsigned int misma if ( !mResult && !mExpectFail ) { + for ( auto it = variantMismatchCount.constBegin(); it != variantMismatchCount.constEnd(); it++ ) + { + if ( subDirs.size() > 1 ) + { + qDebug() << QStringLiteral( "Variant %1: %2/%3 pixels mismatched (%4 allowed)" ).arg( it.key() ).arg( it.value() ).arg( variantSize.value( it.key() ) ).arg( mismatchCount ); + } + else + { + qDebug() << QStringLiteral( "%1/%2 pixels mismatched (%4 allowed)" ).arg( it.value() ).arg( variantSize.value( it.key() ) ).arg( mismatchCount ); + } + } const QDir reportDir = QgsRenderChecker::testReportDir(); if ( !reportDir.exists() ) { diff --git a/src/core/qgsrenderchecker.cpp b/src/core/qgsrenderchecker.cpp index 503a12f9ce4..5d3457b118e 100644 --- a/src/core/qgsrenderchecker.cpp +++ b/src/core/qgsrenderchecker.cpp @@ -523,7 +523,9 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re // Put the same info to debug too // - if ( expectedImage.width() != myResultImage.width() || expectedImage.height() != myResultImage.height() ) + if ( !flags.testFlag( Flag::Silent ) + && ( expectedImage.width() != myResultImage.width() || expectedImage.height() != myResultImage.height() ) + ) { qDebug( "Expected size: %dw x %dh", expectedImage.width(), expectedImage.height() ); qDebug( "Actual size: %dw x %dh", myResultImage.width(), myResultImage.height() ); @@ -533,7 +535,10 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re if ( mMatchTarget != myPixelCount ) { - qDebug( "Expected image and rendered image for %s are different dimensions", testName.toLocal8Bit().constData() ); + if ( !flags.testFlag( Flag::Silent ) ) + { + qDebug( "Expected image and rendered image for %s are different dimensions", testName.toLocal8Bit().constData() ); + } if ( std::abs( expectedImage.width() - myResultImage.width() ) > mMaxSizeDifferenceX || std::abs( expectedImage.height() - myResultImage.height() ) > mMaxSizeDifferenceY ) @@ -690,7 +695,10 @@ bool QgsRenderChecker::compareImages( const QString &testName, const QString &re emitDashMessage( "Rendered Image " + testName + prefix, QgsDartMeasurement::ImagePng, mRenderedImageFile ); emitDashMessage( "Expected Image " + testName + prefix, QgsDartMeasurement::ImagePng, referenceImageFile ); - qDebug( "%d/%d pixels mismatched (%d allowed)", mMismatchCount, mMatchTarget, mismatchCount ); + if ( !flags.testFlag( Flag::Silent ) ) + { + qDebug( "%d/%d pixels mismatched (%d allowed)", mMismatchCount, mMatchTarget, mismatchCount ); + } // //save the diff image to disk diff --git a/src/core/qgsrenderchecker.h b/src/core/qgsrenderchecker.h index f411711d55f..83b3b783e9e 100644 --- a/src/core/qgsrenderchecker.h +++ b/src/core/qgsrenderchecker.h @@ -199,6 +199,7 @@ class CORE_EXPORT QgsRenderChecker enum class Flag : int SIP_ENUM_BASETYPE( IntFlag ) { AvoidExportingRenderedImage = 1 << 0, //!< Avoids exporting rendered images to reports + Silent = 1 << 1, //!< Don't output non-critical messages to console \since QGIS 3.40 }; Q_ENUM( Flag ) diff --git a/src/core/qgstemporalutils.h b/src/core/qgstemporalutils.h index 91d7db3d6b5..03de7048015 100644 --- a/src/core/qgstemporalutils.h +++ b/src/core/qgstemporalutils.h @@ -158,9 +158,9 @@ class CORE_EXPORT QgsTemporalUtils /** * The filename template for exporting the frames. * - * This must be in format prefix####.format, where number of - * \a # characters represents how many 0's should be left-padded to the frame number - * e.g. my###.jpg will create frames my001.jpg, my002.jpg, etc + * This must be in format ``prefix####.format``, where number of + * \a ``#`` characters represents how many 0's should be left-padded to the frame number + * e.g. ``my###.jpg`` will create frames ``my001.jpg``, ``my002.jpg``, etc */ QString fileNameTemplate; diff --git a/src/core/settings/qgssettingsregistrycore.cpp b/src/core/settings/qgssettingsregistrycore.cpp index e0b12336b94..b5364340106 100644 --- a/src/core/settings/qgssettingsregistrycore.cpp +++ b/src/core/settings/qgssettingsregistrycore.cpp @@ -259,6 +259,9 @@ void QgsSettingsRegistryCore::migrateOldSettings() continue; for ( const QString &connection : connections ) { + if ( settings.value( QStringLiteral( "%1/url" ).arg( connection ) ).toString().isEmpty() ) + continue; + QgsOwsConnection::settingsUrl->copyValueFromKey( QStringLiteral( "qgis/connections-%1/%2/url" ), {service.toLower(), connection}, true ); QgsOwsConnection::settingsVersion->copyValueFromKey( QStringLiteral( "qgis/connections-%1/%2/version" ), {service.toLower(), connection}, true ); QgsOwsConnection::settingsIgnoreGetMapURI->copyValueFromKey( QStringLiteral( "qgis/connections-%1/%2/ignoreGetMapURI" ), {service.toLower(), connection}, true ); @@ -296,6 +299,9 @@ void QgsSettingsRegistryCore::migrateOldSettings() const QStringList connections = settings.childGroups(); for ( const QString &connection : connections ) { + if ( settings.value( QStringLiteral( "%1/url" ).arg( connection ) ).toString().isEmpty() ) + continue; + QgsVectorTileProviderConnection::settingsUrl->copyValueFromKey( QStringLiteral( "qgis/connections-vector-tile/%1/url" ), {connection}, true ); QgsVectorTileProviderConnection::settingsZmin->copyValueFromKey( QStringLiteral( "qgis/connections-vector-tile/%1/zmin" ), {connection}, true ); QgsVectorTileProviderConnection::settingsZmax->copyValueFromKey( QStringLiteral( "qgis/connections-vector-tile/%1/zmax" ), {connection}, true ); @@ -320,6 +326,9 @@ void QgsSettingsRegistryCore::migrateOldSettings() const QStringList connections = settings.childGroups(); for ( const QString &connection : connections ) { + if ( settings.value( QStringLiteral( "%1/url" ).arg( connection ) ).toString().isEmpty() ) + continue; + QgsXyzConnectionSettings::settingsUrl->copyValueFromKey( QStringLiteral( "qgis/connections-xyz/%1/url" ), {connection}, true ); QgsXyzConnectionSettings::settingsZmin->copyValueFromKey( QStringLiteral( "qgis/connections-xyz/%1/zmin" ), {connection}, true ); QgsXyzConnectionSettings::settingsZmax->copyValueFromKey( QStringLiteral( "qgis/connections-xyz/%1/zmax" ), {connection}, true ); diff --git a/src/core/stac/qgsstaccatalog.cpp b/src/core/stac/qgsstaccatalog.cpp index 2d2360ef79c..58ed28e8d43 100644 --- a/src/core/stac/qgsstaccatalog.cpp +++ b/src/core/stac/qgsstaccatalog.cpp @@ -58,7 +58,7 @@ QString QgsStacCatalog::toHtml() const html += QStringLiteral( "
    \n" ); for ( const QString &cc : mConformanceClasses ) { - html += QStringLiteral( "
  • %1
  • \n" ).arg( cc ); + html += QStringLiteral( "
  • %1
  • \n" ).arg( cc ); } html += QStringLiteral( "
\n" ); } diff --git a/src/core/stac/qgsstaccollections.cpp b/src/core/stac/qgsstaccollections.cpp new file mode 100644 index 00000000000..87913d64b68 --- /dev/null +++ b/src/core/stac/qgsstaccollections.cpp @@ -0,0 +1,78 @@ +/*************************************************************************** + qgsstaccollections.cpp + --------------------- + begin : October 2024 + copyright : (C) 2024 by Stefanos Natsis + email : uclaros at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsstaccollections.h" + +QgsStacCollections::QgsStacCollections( const QVector< QgsStacCollection * > collections, const QVector< QgsStacLink > links, int numberMatched ) + : mCollections( collections ) + , mNumberMatched( numberMatched ) +{ + for ( const QgsStacLink &link : links ) + { + if ( link.relation() == QLatin1String( "self" ) || + link.relation() == QLatin1String( "root" ) || + link.relation() == QLatin1String( "next" ) || + link.relation() == QLatin1String( "prev" ) ) + mUrls.insert( link.relation(), link.href() ); + } +} + + +QgsStacCollections::~QgsStacCollections() +{ + qDeleteAll( mCollections ); +} + +QVector QgsStacCollections::collections() const +{ + return mCollections; +} + +QVector QgsStacCollections::takeCollections() +{ + QVector< QgsStacCollection * > cols = mCollections; + mCollections.clear(); // detach + return cols; +} + +int QgsStacCollections::numberReturned() const +{ + return mCollections.size(); +} + +int QgsStacCollections::numberMatched() const +{ + return mNumberMatched; +} + +QUrl QgsStacCollections::url() const +{ + return QUrl( mUrls.value( QStringLiteral( "self" ), QString() ) ); +} + +QUrl QgsStacCollections::rootUrl() const +{ + return QUrl( mUrls.value( QStringLiteral( "root" ), QString() ) ); +} + +QUrl QgsStacCollections::nextUrl() const +{ + return QUrl( mUrls.value( QStringLiteral( "next" ), QString() ) ); +} + +QUrl QgsStacCollections::prevUrl() const +{ + return QUrl( mUrls.value( QStringLiteral( "prev" ), QString() ) ); +} diff --git a/src/core/stac/qgsstaccollections.h b/src/core/stac/qgsstaccollections.h new file mode 100644 index 00000000000..140455c0da2 --- /dev/null +++ b/src/core/stac/qgsstaccollections.h @@ -0,0 +1,101 @@ +/*************************************************************************** + qgsstaccollections.h + --------------------- + begin : October 2024 + copyright : (C) 2024 by Stefanos Natsis + email : uclaros at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSSTACCOLLECTIONS_H +#define QGSSTACCOLLECTIONS_H + +#define SIP_NO_FILE + +#include "qgis_core.h" +#include "qgsstaclink.h" + +#include +#include + +class QgsStacCollection; + +/** + * \ingroup core + * \brief Class for storing a list of STAC Collections. + * This is typically used to store the data returned by STAC API /collections endpoint + * \note Not available in python bindings + * \since QGIS 3.40 + */ +class CORE_EXPORT QgsStacCollections +{ + public: + //! Default constructor deleted, use the variant with required parameters + QgsStacCollections() = delete; + + /** + * Constructs a valid list of collections, + * \param collections The STAC Collections to be stored, ownership is transferred + * \param links A list of references to other documents. + * \param numberMatched The total number of collections in the parent catalog, collection or total matching results from a STAC API endpoint + * \note ownership of \a collections is transferred. Collections will be deleted when object is destroyed. + */ + QgsStacCollections( const QVector< QgsStacCollection * > collections, const QVector< QgsStacLink > links, int numberMatched = -1 ); + + //! Destructor + ~QgsStacCollections(); + + /** + * Returns the collections + * Ownership is not transferred + */ + QVector< QgsStacCollection * > collections() const; + + /** + * Returns the collections + * Caller takes ownership of the returned collections + */ + QVector< QgsStacCollection * > takeCollections(); + + //! Returns the number of returned collections + int numberReturned() const; + + /** + * Returns the total number of available collections + * If this information was not available by the STAC server, -1 is returned + */ + int numberMatched() const; + + /** + * Returns the url of the collections' "self" link + */ + QUrl url() const; + + /** + * Returns the url of the collections' "root" link + */ + QUrl rootUrl() const; + + /** + * Returns the url of the collections' "next" link + */ + QUrl nextUrl() const; + + /** + * Returns the url of the collections' "prev" link + */ + QUrl prevUrl() const; + + private: + QVector< QgsStacCollection * > mCollections; + QMap< QString, QString > mUrls; + int mNumberMatched = -1; +}; + +#endif // QGSSTACCOLLECTIONS_H diff --git a/src/core/stac/qgsstaccontroller.cpp b/src/core/stac/qgsstaccontroller.cpp index ad84b16f241..aab1893a66a 100644 --- a/src/core/stac/qgsstaccontroller.cpp +++ b/src/core/stac/qgsstaccontroller.cpp @@ -99,6 +99,7 @@ void QgsStacController::handleStacObjectReply() const QByteArray data = reply->readAll(); QgsStacParser parser; parser.setData( data ); + parser.setBaseUrl( reply->url() ); QgsStacObject *object = nullptr; switch ( parser.type() ) @@ -142,6 +143,7 @@ void QgsStacController::handleItemCollectionReply() const QByteArray data = reply->readAll(); QgsStacParser parser; parser.setData( data ); + parser.setBaseUrl( reply->url() ); QgsStacItemCollection *fc = parser.itemCollection(); mFetchedItemCollections.insert( requestId, fc ); @@ -176,6 +178,7 @@ QgsStacObject *QgsStacController::fetchStacObject( const QUrl &url, QString *err QgsStacParser parser; parser.setData( data ); + parser.setBaseUrl( url ); QgsStacObject *object = nullptr; switch ( parser.type() ) { @@ -215,6 +218,7 @@ QgsStacItemCollection *QgsStacController::fetchItemCollection( const QUrl &url, QgsStacParser parser; parser.setData( data ); + parser.setBaseUrl( url ); QgsStacItemCollection *ic = parser.itemCollection(); if ( error ) @@ -223,6 +227,30 @@ QgsStacItemCollection *QgsStacController::fetchItemCollection( const QUrl &url, return ic; } +QgsStacCollections *QgsStacController::fetchCollections( const QUrl &url, QString *error ) +{ + QgsNetworkReplyContent content = fetchBlocking( url ); + + if ( content.error() != QNetworkReply::NoError ) + { + if ( error ) + *error = content.errorString(); + + return nullptr; + } + + const QByteArray data = content.content(); + + QgsStacParser parser; + parser.setData( data ); + QgsStacCollections *col = parser.collections(); + + if ( error ) + *error = parser.error(); + + return col; +} + QgsNetworkReplyContent QgsStacController::fetchBlocking( const QUrl &url ) { QNetworkRequest req( url ); @@ -263,6 +291,7 @@ QgsStacCatalog *QgsStacController::openLocalCatalog( const QString &fileName ) c QgsStacParser parser; parser.setData( file.readAll() ); + parser.setBaseUrl( fileName ); return parser.catalog(); } @@ -279,6 +308,7 @@ QgsStacCollection *QgsStacController::openLocalCollection( const QString &fileNa QgsStacParser parser; parser.setData( file.readAll() ); + parser.setBaseUrl( fileName ); return parser.collection(); } @@ -294,6 +324,7 @@ QgsStacItem *QgsStacController::openLocalItem( const QString &fileName ) const QgsStacParser parser; parser.setData( file.readAll() ); + parser.setBaseUrl( fileName ); return parser.item(); } diff --git a/src/core/stac/qgsstaccontroller.h b/src/core/stac/qgsstaccontroller.h index 78d10c73240..10039133fd3 100644 --- a/src/core/stac/qgsstaccontroller.h +++ b/src/core/stac/qgsstaccontroller.h @@ -28,6 +28,7 @@ class QgsStacObject; class QgsStacCatalog; class QgsStacCollection; +class QgsStacCollections; class QgsStacItem; class QgsStacItemCollection; class QNetworkReply; @@ -83,6 +84,13 @@ class CORE_EXPORT QgsStacController : public QObject */ QgsStacItemCollection *fetchItemCollection( const QUrl &url, QString *error = nullptr ); + /** + * Fetches collections from \a url using a blocking network request. + * An optional \a error parameter will be populated with any network error information. + * The caller takes ownership of the returned feature collection + */ + QgsStacCollections *fetchCollections( const QUrl &url, QString *error = nullptr ); + /** * Initiates an asynchronous request for a STAC object using the \a url * and returns an associated request id. diff --git a/src/core/stac/qgsstacdataitems.cpp b/src/core/stac/qgsstacdataitems.cpp index b0dc8d7ef52..1977ea9af7c 100644 --- a/src/core/stac/qgsstacdataitems.cpp +++ b/src/core/stac/qgsstacdataitems.cpp @@ -19,6 +19,8 @@ #include "qgsstaccatalog.h" #include "qgsstacitem.h" #include "qgsstacitemcollection.h" +#include "qgsstaccollection.h" +#include "qgsstaccollections.h" constexpr int MAX_DISPLAYED_ITEMS = 20; @@ -332,6 +334,7 @@ QVector QgsStacCatalogItem::createChildren() // treat catalog/collection as static if it does not have a /items endpoint bool hasItemsEndpoint = false; + bool hasCollectionsEndpoint = false; if ( supportsApi ) { for ( const auto &link : links ) @@ -339,8 +342,14 @@ QVector QgsStacCatalogItem::createChildren() if ( link.relation() == QLatin1String( "items" ) ) { hasItemsEndpoint = true; - break; } + else if ( link.relation() == QLatin1String( "data" ) && + link.href().endsWith( QLatin1String( "/collections" ) ) ) + { + hasCollectionsEndpoint = true; + } + if ( hasItemsEndpoint && hasCollectionsEndpoint ) + break; } } @@ -353,12 +362,30 @@ QVector QgsStacCatalogItem::createChildren() link.relation() == QLatin1String( "collection" ) ) continue; - if ( link.relation() == QLatin1String( "child" ) ) + if ( link.relation() == QLatin1String( "child" ) && + !hasCollectionsEndpoint ) { // may be either catalog or collection QgsStacCatalogItem *c = new QgsStacCatalogItem( this, link.title(), link.href() ); contents.append( c ); } + else if ( link.relation() == QLatin1String( "data" ) && + link.href().endsWith( QLatin1String( "/collections" ) ) ) + { + // use /collections api + QString error; + std::unique_ptr< QgsStacCollections > cols( controller->fetchCollections( link.href(), &error ) ); + if ( cols ) + { + contents.append( createCollections( cols->takeCollections() ) ); + itemsCount = cols->numberMatched(); + } + else + { + // collection fetching failed + contents.append( new QgsErrorItem( this, error, path() + QStringLiteral( "/error" ) ) ); + } + } else if ( link.relation() == QLatin1String( "item" ) && !hasItemsEndpoint ) { @@ -370,8 +397,7 @@ QVector QgsStacCatalogItem::createChildren() QgsStacItemItem *i = new QgsStacItemItem( this, link.title(), link.href() ); contents.append( i ); } - else if ( link.relation() == QLatin1String( "items" ) && - hasItemsEndpoint ) + else if ( link.relation() == QLatin1String( "items" ) ) { // stac api items (ogcapi features) QString error; @@ -470,6 +496,24 @@ QVector< QgsDataItem * > QgsStacCatalogItem::createItems( const QVector QgsStacCatalogItem::createCollections( const QVector collections ) +{ + QVector< QgsDataItem * > contents; + contents.reserve( collections.size() ); + for ( QgsStacCollection *col : collections ) + { + if ( !col ) + continue; + + const QString name = col->title().isEmpty() ? col->id() : col->title(); + + QgsStacCatalogItem *i = new QgsStacCatalogItem( this, name, col->url() ); + i->setStacCatalog( col ); + contents.append( i ); + } + return contents; +} + void QgsStacCatalogItem::fetchMoreChildren() { if ( mFetchMoreUrl.isEmpty() ) diff --git a/src/core/stac/qgsstacdataitems.h b/src/core/stac/qgsstacdataitems.h index f7676bb60ef..658a0cdd328 100644 --- a/src/core/stac/qgsstacdataitems.h +++ b/src/core/stac/qgsstacdataitems.h @@ -25,6 +25,7 @@ #include class QgsStacController; +class QgsStacCollection; ///@cond PRIVATE #define SIP_NO_FILE @@ -108,6 +109,7 @@ class CORE_EXPORT QgsStacCatalogItem : public QgsDataCollectionItem private: //! takes ownership QVector< QgsDataItem * > createItems( const QVector items ); + QVector< QgsDataItem * > createCollections( const QVector collections ); //! The URI QString mUri; diff --git a/src/core/stac/qgsstacitemcollection.h b/src/core/stac/qgsstacitemcollection.h index a0e8adf732d..141c8f593a1 100644 --- a/src/core/stac/qgsstacitemcollection.h +++ b/src/core/stac/qgsstacitemcollection.h @@ -19,7 +19,12 @@ #define SIP_NO_FILE #include "qgis_core.h" -#include "qgsstacitem.h" +#include "qgsstaclink.h" + +#include +#include + +class QgsStacItem; /** * \ingroup core diff --git a/src/core/stac/qgsstacparser.cpp b/src/core/stac/qgsstacparser.cpp index 76fbabaee03..be0c19bbb87 100644 --- a/src/core/stac/qgsstacparser.cpp +++ b/src/core/stac/qgsstacparser.cpp @@ -14,6 +14,11 @@ ***************************************************************************/ #include "qgsstacparser.h" +#include "qgsstacitem.h" +#include "qgsstaccatalog.h" +#include "qgsstaccollection.h" +#include "qgsstaccollections.h" +#include "qgsstacitemcollection.h" #include "qgsjsonutils.h" #include "qgslogger.h" @@ -49,6 +54,11 @@ void QgsStacParser::setData( const QByteArray &data ) } } +void QgsStacParser::setBaseUrl( const QUrl &url ) +{ + mBaseUrl = url; +} + QgsStacObject::Type QgsStacParser::type() const { return mType; @@ -60,43 +70,50 @@ QString QgsStacParser::error() const } QgsStacCatalog *QgsStacParser::catalog() +{ + return parseCatalog( mData ); +} + +QgsStacCatalog *QgsStacParser::parseCatalog( const nlohmann::json &data ) { try { - const QString ver( QString::fromStdString( mData.at( "stac_version" ) ) ); + const QString ver( QString::fromStdString( data.at( "stac_version" ) ) ); if ( !isSupportedStacVersion( ver ) ) { mError = QStringLiteral( "Unsupported STAC version: %1" ).arg( ver ); return nullptr; } - const QString id( QString::fromStdString( mData.at( "id" ) ) ); - const QString description( QString::fromStdString( mData.at( "description" ) ) ); + const QString id( QString::fromStdString( data.at( "id" ) ) ); + const QString description( getString( data.at( "description" ) ) ); - QVector< QgsStacLink > links = parseLinks( mData.at( "links" ) ); + QVector< QgsStacLink > links = parseLinks( data.at( "links" ) ); std::unique_ptr< QgsStacCatalog > catalog = std::make_unique< QgsStacCatalog >( id, ver, description, links ); - if ( mData.contains( "title" ) ) - catalog->setTitle( QString::fromStdString( mData["title"] ) ); + if ( data.contains( "title" ) ) + catalog->setTitle( getString( data["title"] ) ); - if ( mData.contains( "conformsTo" ) ) + if ( data.contains( "conformsTo" ) ) { - for ( const auto &conformanceClass : mData["conformsTo"] ) + for ( const auto &conformanceClass : data["conformsTo"] ) { - catalog->addConformanceClass( QString::fromStdString( conformanceClass ) ); + if ( conformanceClass.is_string() ) + catalog->addConformanceClass( QString::fromStdString( conformanceClass ) ); } } - if ( mData.contains( "stac_extensions" ) ) + if ( data.contains( "stac_extensions" ) ) { QStringList extensions; - for ( const auto &extension : mData["stac_extensions"] ) + for ( const auto &extension : data["stac_extensions"] ) { - extensions.append( QString::fromStdString( extension ) ); + if ( extension.is_string() ) + extensions.append( QString::fromStdString( extension ) ); } catalog->setStacExtensions( extensions ); } @@ -112,23 +129,28 @@ QgsStacCatalog *QgsStacParser::catalog() } QgsStacCollection *QgsStacParser::collection() +{ + return parseCollection( mData ); +} + +QgsStacCollection *QgsStacParser::parseCollection( const nlohmann::json &data ) { try { - const QString ver( QString::fromStdString( mData.at( "stac_version" ) ) ); + const QString ver( QString::fromStdString( data.at( "stac_version" ) ) ); if ( !isSupportedStacVersion( ver ) ) { mError = QStringLiteral( "Unsupported STAC version: %1" ).arg( ver ); return nullptr; } - const QString id( QString::fromStdString( mData.at( "id" ) ) ); - const QString description( QString::fromStdString( mData.at( "description" ) ) ); - const QString license( QString::fromStdString( mData.at( "license" ) ) ); + const QString id( QString::fromStdString( data.at( "id" ) ) ); + const QString description( getString( data.at( "description" ) ) ); + const QString license( getString( data.at( "license" ) ) ); QgsStacExtent stacExtent; int totalExtents = 0; - for ( const auto &e : mData.at( "extent" ).at( "spatial" ).at( "bbox" ) ) + for ( const auto &e : data.at( "extent" ).at( "spatial" ).at( "bbox" ) ) { QgsBox3D extent; if ( e.size() == 4 ) @@ -162,7 +184,7 @@ QgsStacCollection *QgsStacParser::collection() } totalExtents = 0; - for ( const auto &e : mData.at( "extent" ).at( "temporal" ).at( "interval" ) ) + for ( const auto &e : data.at( "extent" ).at( "temporal" ).at( "interval" ) ) { if ( !e.is_array() || e.size() != 2 ) @@ -171,8 +193,8 @@ QgsStacCollection *QgsStacParser::collection() QgsDebugError( mError ); return nullptr; } - const QDateTime start = e[0].is_null() ? QDateTime() : QDateTime::fromString( QString::fromStdString( e[0] ), Qt::ISODateWithMs ); - const QDateTime end = e[1].is_null() ? QDateTime() : QDateTime::fromString( QString::fromStdString( e[1] ), Qt::ISODateWithMs ); + const QDateTime start = QDateTime::fromString( getString( e[0] ), Qt::ISODateWithMs ); + const QDateTime end = QDateTime::fromString( getString( e[1] ), Qt::ISODateWithMs ); if ( ++totalExtents == 1 ) stacExtent.setTemporalExtent( QgsDateTimeRange( start, end ) ); @@ -180,7 +202,7 @@ QgsStacCollection *QgsStacParser::collection() stacExtent.addDetailedTemporalExtent( QgsDateTimeRange( start, end ) ); } - QVector< QgsStacLink > links = parseLinks( mData.at( "links" ) ); + QVector< QgsStacLink > links = parseLinks( data.at( "links" ) ); std::unique_ptr< QgsStacCollection > collection = std::make_unique< QgsStacCollection >( id, ver, @@ -189,33 +211,35 @@ QgsStacCollection *QgsStacParser::collection() license, stacExtent ); - if ( mData.contains( "title" ) ) - collection->setTitle( QString::fromStdString( mData["title"] ) ); + if ( data.contains( "title" ) ) + collection->setTitle( getString( data["title"] ) ); - if ( mData.contains( "stac_extensions" ) ) + if ( data.contains( "stac_extensions" ) ) { QStringList extensions; - for ( const auto &extension : mData["stac_extensions"] ) + for ( const auto &extension : data["stac_extensions"] ) { - extensions.append( QString::fromStdString( extension ) ); + if ( extension.is_string() ) + extensions.append( QString::fromStdString( extension ) ); } collection->setStacExtensions( extensions ); } - if ( mData.contains( "keywords" ) ) + if ( data.contains( "keywords" ) ) { QStringList keywords; - for ( const auto &kw : mData["keywords"] ) + for ( const auto &kw : data["keywords"] ) { - keywords.append( QString::fromStdString( kw ) ); + if ( kw.is_string() ) + keywords.append( QString::fromStdString( kw ) ); } collection->setKeywords( keywords ); } - if ( mData.contains( "providers" ) ) + if ( data.contains( "providers" ) ) { QVector< QgsStacProvider > providers; - for ( const auto &p : mData["providers"] ) + for ( const auto &p : data["providers"] ) { if ( !p.contains( "name" ) || ( p.contains( "roles" ) && !p["roles"].is_array() ) ) @@ -229,13 +253,14 @@ QgsStacCollection *QgsStacParser::collection() { for ( const auto &role : p["roles"] ) { - roles.append( QString::fromStdString( role ) ); + if ( role.is_string() ) + roles.append( QString::fromStdString( role ) ); } } const QgsStacProvider provider( QString::fromStdString( p["name"] ), - p.contains( "description" ) ? QString::fromStdString( p["description"] ) : QString(), + p.contains( "description" ) ? getString( p["description"] ) : QString(), roles, - p.contains( "url" ) ? QString::fromStdString( p["url"] ) : QString() ); + p.contains( "url" ) ? getString( p["url"] ) : QString() ); @@ -244,15 +269,15 @@ QgsStacCollection *QgsStacParser::collection() collection->setProviders( providers ); } - if ( mData.contains( "summaries" ) ) + if ( data.contains( "summaries" ) ) { - const QVariant summ = QgsJsonUtils::jsonToVariant( mData["summaries"] ); + const QVariant summ = QgsJsonUtils::jsonToVariant( data["summaries"] ); collection->setSummaries( summ.toMap() ); } - if ( mData.contains( "assets" ) ) + if ( data.contains( "assets" ) ) { - QMap< QString, QgsStacAsset > assets = parseAssets( mData["assets"] ); + QMap< QString, QgsStacAsset > assets = parseAssets( data["assets"] ); collection->setAssets( assets ); } @@ -345,13 +370,14 @@ QgsStacItem *QgsStacParser::parseItem( const nlohmann::json &data ) QStringList extensions; for ( const auto &extension : data["stac_extensions"] ) { - extensions.append( QString::fromStdString( extension ) ); + if ( extension.is_string() ) + extensions.append( QString::fromStdString( extension ) ); } item->setStacExtensions( extensions ); } if ( data.contains( "collection" ) ) - item->setCollection( QString::fromStdString( data["collection"] ) ); + item->setCollection( getString( data["collection"] ) ); return item.release(); } @@ -369,10 +395,14 @@ QVector QgsStacParser::parseLinks( const json &data ) links.reserve( static_cast( data.size() ) ); for ( const auto &link : data ) { - const QgsStacLink l( QString::fromStdString( link.at( "href" ) ), + QUrl linkUrl( QString::fromStdString( link.at( "href" ) ) ); + if ( linkUrl.isRelative() ) + linkUrl = mBaseUrl.resolved( linkUrl ); + + const QgsStacLink l( linkUrl.toString(), QString::fromStdString( link.at( "rel" ) ), - link.contains( "type" ) ? QString::fromStdString( link["type"] ) : QString(), - link.contains( "title" ) ? QString::fromStdString( link["title"] ) : QString() ); + link.contains( "type" ) ? getString( link["type"] ) : QString(), + link.contains( "title" ) ? getString( link["title"] ) : QString() ); links.append( l ); } return links; @@ -384,10 +414,14 @@ QMap QgsStacParser::parseAssets( const json &data ) for ( const auto &asset : data.items() ) { const json value = asset.value(); - const QgsStacAsset a( QString::fromStdString( value.at( "href" ) ), - value.contains( "title" ) ? QString::fromStdString( value["title"] ) : QString(), - value.contains( "description" ) ? QString::fromStdString( value["description"] ) : QString(), - value.contains( "type" ) ? QString::fromStdString( value["type"] ) : QString(), + QUrl assetUrl( QString::fromStdString( value.at( "href" ) ) ); + if ( assetUrl.isRelative() ) + assetUrl = mBaseUrl.resolved( assetUrl ); + + const QgsStacAsset a( assetUrl.toString(), + value.contains( "title" ) ? getString( value["title"] ) : QString(), + value.contains( "description" ) ? getString( value["description"] ) : QString(), + value.contains( "type" ) ? getString( value["type"] ) : QString(), value.contains( "roles" ) ? QgsJsonUtils::jsonToVariant( value["roles"] ).toStringList() : QStringList() ); assets.insert( QString::fromStdString( asset.key() ), a ); } @@ -425,6 +459,11 @@ bool QgsStacParser::isSupportedStacVersion( const QString &version ) return true; } +QString QgsStacParser::getString( const nlohmann::json &data ) +{ + return data.is_null() ? QString() : QString::fromStdString( data ); +} + QgsStacItemCollection *QgsStacParser::itemCollection() { try @@ -451,3 +490,30 @@ QgsStacItemCollection *QgsStacParser::itemCollection() return nullptr; } } + +QgsStacCollections *QgsStacParser::collections() +{ + try + { + QVector< QgsStacLink > links = parseLinks( mData.at( "links" ) ); + + QVector< QgsStacCollection * > cols; + cols.reserve( static_cast( mData.at( "collections" ).size() ) ); + for ( auto &col : mData.at( "collections" ) ) + { + QgsStacCollection *c = parseCollection( col ); + if ( c ) + cols.append( c ); + } + + const int numberMatched = mData.contains( "numberMatched" ) ? mData["numberMatched"].get() : -1; + + return new QgsStacCollections( cols, links, numberMatched ); + } + catch ( nlohmann::json::exception &ex ) + { + mError = QStringLiteral( "Error parsing ItemCollection" ); + QgsDebugError( QStringLiteral( "Error parsing ItemCollection: %1" ).arg( ex.what() ) ); + return nullptr; + } +} diff --git a/src/core/stac/qgsstacparser.h b/src/core/stac/qgsstacparser.h index ce1c15070d8..137b0f28ae7 100644 --- a/src/core/stac/qgsstacparser.h +++ b/src/core/stac/qgsstacparser.h @@ -19,11 +19,17 @@ #define SIP_NO_FILE #include +#include + +#include "qgsstacobject.h" +#include "qgsstacasset.h" + +class QgsStacCatalog; +class QgsStacCollection; +class QgsStacCollections; +class QgsStacItem; +class QgsStacItemCollection; -#include "qgsstacitem.h" -#include "qgsstaccollection.h" -#include "qgsstaccatalog.h" -#include "qgsstacitemcollection.h" /** * \brief SpatioTemporal Asset Catalog JSON parser @@ -42,6 +48,12 @@ class QgsStacParser //! Sets the JSON \data to be parsed void setData( const QByteArray &data ); + /** + * Sets the base \a url that will be used to resolve relative links. + * If not called, relative links will not be resolved to absolute links. + */ + void setBaseUrl( const QUrl &url ); + /** * Returns the parsed STAC Catalog * If parsing failed, NULLPTR is returned @@ -70,6 +82,13 @@ class QgsStacParser */ QgsStacItemCollection *itemCollection(); + /** + * Returns the parsed STAC API Collections + * If parsing failed, NULLPTR is returned + * The caller takes ownership of the returned collections + */ + QgsStacCollections *collections(); + //! Returns the type of the parsed object QgsStacObject::Type type() const; @@ -81,15 +100,17 @@ class QgsStacParser QgsStacCatalog *parseCatalog( const nlohmann::json &data ); QgsStacCollection *parseCollection( const nlohmann::json &data ); - static QVector< QgsStacLink > parseLinks( const nlohmann::json &data ); - static QMap< QString, QgsStacAsset > parseAssets( const nlohmann::json &data ); + QVector< QgsStacLink > parseLinks( const nlohmann::json &data ); + QMap< QString, QgsStacAsset > parseAssets( const nlohmann::json &data ); static bool isSupportedStacVersion( const QString &version ); + //! Returns a QString, treating null elements as empty strings + static QString getString( const nlohmann::json &data ); nlohmann::json mData; QgsStacObject::Type mType = QgsStacObject::Type::Unknown; std::unique_ptr mObject; QString mError; - + QUrl mBaseUrl; }; diff --git a/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp b/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp index 80c138dff0c..3546320dd1c 100644 --- a/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp +++ b/src/core/symbology/qgsgeometrygeneratorsymbollayer.cpp @@ -14,6 +14,7 @@ ***************************************************************************/ #include "qgsgeometrygeneratorsymbollayer.h" +#include "qgsexpressionutils.h" #include "qgsgeometry.h" #include "qgsmarkersymbol.h" #include "qgslinesymbol.h" @@ -337,7 +338,8 @@ QgsGeometry QgsGeometryGeneratorSymbolLayer::evaluateGeometryInPainterUnits( con generatorScope->setGeometry( drawGeometry ); // step 3 - evaluate the new generated geometry. - QgsGeometry geom = mExpression->evaluate( &expressionContext ).value(); + QVariant value = mExpression->evaluate( &expressionContext ); + QgsGeometry geom = QgsExpressionUtils::getGeometry( value, mExpression.get() ); // step 4 - transform geometry back from target units to painter units geom.transform( painterToTargetUnits.inverted( ) ); @@ -461,8 +463,8 @@ void QgsGeometryGeneratorSymbolLayer::render( QgsSymbolRenderContext &context, Q case Qgis::RenderUnit::MetersInMapUnits: // unsupported, not exposed as an option case Qgis::RenderUnit::Percentage: // unsupported, not exposed as an option { - QgsGeometry geom = mExpression->evaluate( &expressionContext ).value(); - f.setGeometry( coerceToExpectedType( geom ) ); + QVariant value = mExpression->evaluate( &expressionContext ); + f.setGeometry( coerceToExpectedType( QgsExpressionUtils::getGeometry( value, mExpression.get() ) ) ); break; } diff --git a/src/core/textrenderer/qgstextformat.cpp b/src/core/textrenderer/qgstextformat.cpp index 953f5a04309..09664c7ca8a 100644 --- a/src/core/textrenderer/qgstextformat.cpp +++ b/src/core/textrenderer/qgstextformat.cpp @@ -214,6 +214,7 @@ void QgsTextFormat::setFont( const QFont &font ) { d->isValid = true; d->textFont = font; + d->originalFontFamily.clear(); } QString QgsTextFormat::namedStyle() const @@ -466,7 +467,8 @@ void QgsTextFormat::readFromLayer( QgsVectorLayer *layer ) { d->isValid = true; QFont appFont = QApplication::font(); - mTextFontFamily = QgsApplication::fontManager()->processFontFamilyName( layer->customProperty( QStringLiteral( "labeling/fontFamily" ), QVariant( appFont.family() ) ).toString() ); + d->originalFontFamily = QgsApplication::fontManager()->processFontFamilyName( layer->customProperty( QStringLiteral( "labeling/fontFamily" ), QVariant( appFont.family() ) ).toString() ); + mTextFontFamily = d->originalFontFamily; QString fontFamily = mTextFontFamily; if ( mTextFontFamily != appFont.family() && !QgsFontUtils::fontFamilyMatchOnSystem( mTextFontFamily ) ) { @@ -555,7 +557,8 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext else textStyleElem = elem.firstChildElement( QStringLiteral( "text-style" ) ); QFont appFont = QApplication::font(); - mTextFontFamily = QgsApplication::fontManager()->processFontFamilyName( textStyleElem.attribute( QStringLiteral( "fontFamily" ), appFont.family() ) ); + d->originalFontFamily = QgsApplication::fontManager()->processFontFamilyName( textStyleElem.attribute( QStringLiteral( "fontFamily" ), appFont.family() ) ); + mTextFontFamily = d->originalFontFamily; QString fontFamily = mTextFontFamily; const QDomElement familiesElem = textStyleElem.firstChildElement( QStringLiteral( "families" ) ); @@ -749,7 +752,7 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex { // text style QDomElement textStyleElem = doc.createElement( QStringLiteral( "text-style" ) ); - textStyleElem.setAttribute( QStringLiteral( "fontFamily" ), d->textFont.family() ); + textStyleElem.setAttribute( QStringLiteral( "fontFamily" ), !d->originalFontFamily.isEmpty() ? d->originalFontFamily : d->textFont.family() ); QDomElement familiesElem = doc.createElement( QStringLiteral( "families" ) ); for ( const QString &family : std::as_const( d->families ) ) diff --git a/src/core/textrenderer/qgstextrenderer_p.h b/src/core/textrenderer/qgstextrenderer_p.h index 190ae64abd8..91169f01f66 100644 --- a/src/core/textrenderer/qgstextrenderer_p.h +++ b/src/core/textrenderer/qgstextrenderer_p.h @@ -264,6 +264,7 @@ class QgsTextSettingsPrivate : public QSharedData QgsTextSettingsPrivate( const QgsTextSettingsPrivate &other ) : QSharedData( other ) , isValid( other.isValid ) + , originalFontFamily( other.originalFontFamily ) , textFont( other.textFont ) , families( other.families ) , textNamedStyle( other.textNamedStyle ) @@ -289,6 +290,8 @@ class QgsTextSettingsPrivate : public QSharedData } bool isValid = false; + + QString originalFontFamily; QFont textFont; QStringList families; QString textNamedStyle; diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.cpp b/src/core/vector/qgsvectorlayerprofilegenerator.cpp index a77d368089b..17f6cae20a9 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.cpp +++ b/src/core/vector/qgsvectorlayerprofilegenerator.cpp @@ -15,6 +15,7 @@ * * ***************************************************************************/ #include "qgsvectorlayerprofilegenerator.h" +#include "qgsabstractgeometry.h" #include "qgspolyhedralsurface.h" #include "qgsprofilerequest.h" #include "qgscurve.h" @@ -721,6 +722,72 @@ bool QgsVectorLayerProfileGenerator::generateProfile( const QgsProfileGeneration if ( !mProfileCurve || mFeedback->isCanceled() ) return false; + if ( QgsLineString *profileLine = + qgsgeometry_cast( mProfileCurve.get() ) ) + { + // The profile generation code can't deal with curves that enter a single + // point multiple times. We handle this for line strings by splitting them + // into multiple parts, each with no repeated points, and computing the + // profile for each by itself. + std::unique_ptr< QgsCurve > origCurve = std::move( mProfileCurve ); + std::unique_ptr< QgsVectorLayerProfileResults > totalResults; + double distanceProcessed = 0; + + QVector disjointParts = profileLine->splitToDisjointXYParts(); + for ( int i = 0; i < disjointParts.size(); i++ ) + { + mProfileCurve.reset( disjointParts[i] ); + if ( !generateProfileInner() ) + { + mProfileCurve = std::move( origCurve ); + + // Free the rest of the parts + for ( int j = i + 1; j < disjointParts.size(); j++ ) + delete disjointParts[j]; + + return false; + } + + if ( !totalResults ) + // Use the first result set as a base + totalResults.reset( mResults.release() ); + else + { + // Merge the results, shifting them by distanceProcessed + totalResults->mRawPoints.append( mResults->mRawPoints ); + totalResults->minZ = std::min( totalResults->minZ, mResults->minZ ); + totalResults->maxZ = std::max( totalResults->maxZ, mResults->maxZ ); + for ( auto it = mResults->mDistanceToHeightMap.constKeyValueBegin(); + it != mResults->mDistanceToHeightMap.constKeyValueEnd(); + ++it ) + { + totalResults->mDistanceToHeightMap[it->first + distanceProcessed] = it->second; + } + for ( auto it = mResults->features.constKeyValueBegin(); + it != mResults->features.constKeyValueEnd(); + ++it ) + { + for ( QgsVectorLayerProfileResults::Feature feature : it->second ) + { + feature.crossSectionGeometry.translate( distanceProcessed, 0 ); + totalResults->features[it->first].push_back( feature ); + } + } + } + + distanceProcessed += mProfileCurve->length(); + } + + mProfileCurve = std::move( origCurve ); + mResults.reset( totalResults.release() ); + return true; + } + + return generateProfileInner(); +} + +bool QgsVectorLayerProfileGenerator::generateProfileInner( const QgsProfileGenerationContext & ) +{ // we need to transform the profile curve to the vector's CRS mTransformedCurve.reset( mProfileCurve->clone() ); mLayerToTargetTransform = QgsCoordinateTransform( mSourceCrs, mTargetCrs, mTransformContext ); diff --git a/src/core/vector/qgsvectorlayerprofilegenerator.h b/src/core/vector/qgsvectorlayerprofilegenerator.h index 17ae6130298..8bc64780110 100644 --- a/src/core/vector/qgsvectorlayerprofilegenerator.h +++ b/src/core/vector/qgsvectorlayerprofilegenerator.h @@ -120,6 +120,10 @@ class CORE_EXPORT QgsVectorLayerProfileGenerator : public QgsAbstractProfileSurf private: + // We may need to split mProfileCurve into multiple parts, this will be + // called for each part. + bool generateProfileInner( const QgsProfileGenerationContext &context = QgsProfileGenerationContext() ); + bool generateProfileForPoints(); bool generateProfileForLines(); bool generateProfileForPolygons(); diff --git a/src/gui/elevation/qgselevationprofilecanvas.cpp b/src/gui/elevation/qgselevationprofilecanvas.cpp index e2addc00cae..cbe6cfe0a77 100644 --- a/src/gui/elevation/qgselevationprofilecanvas.cpp +++ b/src/gui/elevation/qgselevationprofilecanvas.cpp @@ -864,13 +864,7 @@ void QgsElevationProfileCanvas::refresh() if ( !mProject || !profileCurve() ) return; - if ( mCurrentJob ) - { - mPlotItem->setRenderer( nullptr ); - disconnect( mCurrentJob, &QgsProfilePlotRenderer::generationFinished, this, &QgsElevationProfileCanvas::generationFinished ); - mCurrentJob->deleteLater(); - mCurrentJob = nullptr; - } + cancelJobs(); QgsProfileRequest request( profileCurve()->clone() ); request.setCrs( mCrs ); @@ -1434,7 +1428,7 @@ QVector QgsElevationProfileCanvas::identify( const QR void QgsElevationProfileCanvas::clear() { setProfileCurve( nullptr ); - mPlotItem->setRenderer( nullptr ); + cancelJobs(); mPlotItem->updatePlot(); } diff --git a/src/gui/layout/qgslayoutimageexportoptionsdialog.cpp b/src/gui/layout/qgslayoutimageexportoptionsdialog.cpp index d3ea7555572..6b5e0251aa6 100644 --- a/src/gui/layout/qgslayoutimageexportoptionsdialog.cpp +++ b/src/gui/layout/qgslayoutimageexportoptionsdialog.cpp @@ -23,9 +23,12 @@ #include #include +#include +#include -QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *parent, Qt::WindowFlags flags ) +QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *parent, const QString &fileExtension, Qt::WindowFlags flags ) : QDialog( parent, flags ) + , mFileExtension( fileExtension ) { setupUi( this ); connect( mWidthSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged ); @@ -34,6 +37,16 @@ QgsLayoutImageExportOptionsDialog::QgsLayoutImageExportOptionsDialog( QWidget *p connect( mClipToContentGroupBox, &QGroupBox::toggled, this, &QgsLayoutImageExportOptionsDialog::clipToContentsToggled ); connect( mHelpButtonBox, &QDialogButtonBox::helpRequested, this, &QgsLayoutImageExportOptionsDialog::showHelp ); + + const bool showQuality = shouldShowQuality(); + mQualitySpinBox->setVisible( showQuality ); + mQualitySlider->setVisible( showQuality ); + mQualityLabel->setVisible( showQuality ); + mQualityLabel->setText( tr( "%1 quality", "Image format" ).arg( mFileExtension.toUpper() ) ); + + connect( mQualitySpinBox, qOverload< int >( &QSpinBox::valueChanged ), mQualitySlider, &QSlider::setValue ); + connect( mQualitySlider, &QSlider::valueChanged, mQualitySpinBox, &QSpinBox::setValue ); + QgsGui::enableAutoGeometryRestore( this ); } @@ -142,6 +155,20 @@ void QgsLayoutImageExportOptionsDialog::setOpenAfterExporting( bool enabled ) mOpenAfterExportingCheckBox->setChecked( enabled ); } +int QgsLayoutImageExportOptionsDialog::quality() const +{ + if ( !shouldShowQuality() ) + { + return -1; + } + return mQualitySpinBox->value(); +} + +void QgsLayoutImageExportOptionsDialog::setQuality( int quality ) +{ + mQualitySpinBox->setValue( quality ); +} + void QgsLayoutImageExportOptionsDialog::mWidthSpinBox_valueChanged( int value ) { mHeightSpinBox->blockSignals( true ); @@ -201,3 +228,16 @@ void QgsLayoutImageExportOptionsDialog::showHelp() { QgsHelp::openHelp( QStringLiteral( "print_composer/create_output.html" ) ); } + +bool QgsLayoutImageExportOptionsDialog::shouldShowQuality() const +{ + const QStringList validExtensions = { "jpeg", "jpg" }; + for ( const QString &ext : validExtensions ) + { + if ( mFileExtension.toLower() == ext ) + { + return true; + } + } + return false; +} diff --git a/src/gui/layout/qgslayoutimageexportoptionsdialog.h b/src/gui/layout/qgslayoutimageexportoptionsdialog.h index 0c2ff746f60..f1af69a3879 100644 --- a/src/gui/layout/qgslayoutimageexportoptionsdialog.h +++ b/src/gui/layout/qgslayoutimageexportoptionsdialog.h @@ -41,9 +41,10 @@ class GUI_EXPORT QgsLayoutImageExportOptionsDialog: public QDialog, private Ui:: /** * Constructor for QgsLayoutImageExportOptionsDialog * \param parent parent widget + * \param fileExtension output image file extension * \param flags window flags */ - QgsLayoutImageExportOptionsDialog( QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags() ); + QgsLayoutImageExportOptionsDialog( QWidget *parent = nullptr, const QString &fileExtension = QString(), Qt::WindowFlags flags = Qt::WindowFlags() ); /** * Sets the initial resolution displayed in the dialog. @@ -138,6 +139,12 @@ class GUI_EXPORT QgsLayoutImageExportOptionsDialog: public QDialog, private Ui:: //! Returns whether the pdf should be opened after exporting it bool openAfterExporting() const; + + //! Sets the image quality (for JPEG) + void setQuality( int quality ); + //! Returns the image quality + int quality() const; + private slots: void mWidthSpinBox_valueChanged( int value ); @@ -148,7 +155,9 @@ class GUI_EXPORT QgsLayoutImageExportOptionsDialog: public QDialog, private Ui:: private: + bool shouldShowQuality() const; QSizeF mImageSize; + QString mFileExtension; }; diff --git a/src/gui/layout/qgslayoutpagepropertieswidget.cpp b/src/gui/layout/qgslayoutpagepropertieswidget.cpp index a03e71e8717..eb700688092 100644 --- a/src/gui/layout/qgslayoutpagepropertieswidget.cpp +++ b/src/gui/layout/qgslayoutpagepropertieswidget.cpp @@ -83,12 +83,16 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q mSymbolButton->registerExpressionContextGenerator( mPage ); mSymbolButton->setLayer( coverageLayer() ); + + connect( mApplyToAllButton, &QPushButton::clicked, this, &QgsLayoutPagePropertiesWidget::applyToAll ); + if ( mPage->layout() ) { connect( &mPage->layout()->reportContext(), &QgsLayoutReportContext::layerChanged, mSymbolButton, &QgsSymbolButton::setLayer ); QgsLayoutPageCollection *pages = mPage->layout()->pageCollection(); - if ( pages->pageCount() > 1 ) + const bool multiPage = pages->pageCount() > 1; + if ( multiPage ) { const int pageNumber = mPage->layout()->pageCollection()->pageNumber( mPage ); mTitleLabel->setText( tr( "Page (%1/%2)" ).arg( pageNumber + 1 ).arg( pages->pageCount() ) ); @@ -97,6 +101,7 @@ QgsLayoutPagePropertiesWidget::QgsLayoutPagePropertiesWidget( QWidget *parent, Q { mTitleLabel->setText( tr( "Page" ) ); } + mApplyToAllButton->setVisible( multiPage ); } @@ -236,3 +241,9 @@ void QgsLayoutPagePropertiesWidget::showCurrentPageSize() mPageOrientationComboBox->setEnabled( false ); } } + +void QgsLayoutPagePropertiesWidget::applyToAll() +{ + QgsLayoutPageCollection *pages = mPage->layout()->pageCollection(); + pages->applyPropertiesToAllOtherPages( pages->pageNumber( mPage ) ); +} diff --git a/src/gui/layout/qgslayoutpagepropertieswidget.h b/src/gui/layout/qgslayoutpagepropertieswidget.h index 2ab0c192a64..b501f8a769b 100644 --- a/src/gui/layout/qgslayoutpagepropertieswidget.h +++ b/src/gui/layout/qgslayoutpagepropertieswidget.h @@ -64,6 +64,7 @@ class GUI_EXPORT QgsLayoutPagePropertiesWidget : public QgsLayoutItemBaseWidget, void symbolChanged(); void excludeExportsToggled( bool checked ); void refreshLayout(); + void applyToAll(); private: diff --git a/src/gui/layout/qgslayoutviewtooladdnodeitem.cpp b/src/gui/layout/qgslayoutviewtooladdnodeitem.cpp index edacfc38ac9..a8ff386f309 100644 --- a/src/gui/layout/qgslayoutviewtooladdnodeitem.cpp +++ b/src/gui/layout/qgslayoutviewtooladdnodeitem.cpp @@ -69,15 +69,22 @@ void QgsLayoutViewToolAddNodeItem::layoutPressEvent( QgsLayoutViewMouseEvent *ev // last (temporary) point is removed mPolygon.remove( mPolygon.count() - 1 ); - QgsLayoutItem *item = QgsGui::layoutItemGuiRegistry()->createItem( mItemMetadataId, layout() ); + std::unique_ptr< QgsLayoutItem > item( QgsGui::layoutItemGuiRegistry()->createItem( mItemMetadataId, layout() ) ); if ( !item ) return; - if ( QgsLayoutNodesItem *nodesItem = qobject_cast< QgsLayoutNodesItem * >( item ) ) + if ( QgsLayoutNodesItem *nodesItem = qobject_cast< QgsLayoutNodesItem * >( item.get() ) ) + { nodesItem->setNodes( mPolygon ); - - layout()->addLayoutItem( item ); - layout()->setSelectedItem( item ); + if ( !nodesItem->isValid() ) + { + mRubberBand.reset(); + return; + } + } + QgsLayoutItem *newItem = item.get(); + layout()->addLayoutItem( item.release() ); + layout()->setSelectedItem( newItem ); emit createdItem(); } else diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp index c793ecf1de6..91006795a00 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp @@ -68,6 +68,8 @@ #include "qgsprocessingpointcloudexpressionlineedit.h" #include "qgsprocessingrastercalculatorexpressionlineedit.h" #include "qgsunittypes.h" +#include "qgsgeometrywidget.h" + #include #include #include @@ -4427,23 +4429,24 @@ QgsProcessingGeometryParameterDefinitionWidget::QgsProcessingGeometryParameterDe vlayout->addWidget( new QLabel( tr( "Default value" ) ) ); - mDefaultLineEdit = new QLineEdit(); - mDefaultLineEdit->setToolTip( tr( "Geometry as WKT" ) ); - mDefaultLineEdit->setPlaceholderText( tr( "Geometry as WKT" ) ); + mGeometryWidget = new QgsGeometryWidget(); if ( const QgsProcessingParameterGeometry *geometryParam = dynamic_cast( definition ) ) { QgsGeometry g = QgsProcessingParameters::parameterAsGeometry( geometryParam, geometryParam->defaultValueForGui(), context ); if ( !g.isNull() ) - mDefaultLineEdit->setText( g.asWkt() ); + { + mGeometryWidget->setGeometryValue( QgsReferencedGeometry( g, QgsCoordinateReferenceSystem() ) ); + } } - vlayout->addWidget( mDefaultLineEdit ); + vlayout->addWidget( mGeometryWidget ); setLayout( vlayout ); } QgsProcessingParameterDefinition *QgsProcessingGeometryParameterDefinitionWidget::createParameter( const QString &name, const QString &description, Qgis::ProcessingParameterFlags flags ) const { - auto param = std::make_unique< QgsProcessingParameterGeometry >( name, description, mDefaultLineEdit->text() ); + const QgsReferencedGeometry geometry = mGeometryWidget->geometryValue(); + auto param = std::make_unique< QgsProcessingParameterGeometry >( name, description, geometry.isEmpty() ? QVariant() : geometry.asWkt() ); param->setFlags( flags ); return param.release(); } @@ -4462,13 +4465,13 @@ QWidget *QgsProcessingGeometryWidgetWrapper::createWidget() case QgsProcessingGui::Modeler: case QgsProcessingGui::Batch: { - mLineEdit = new QLineEdit(); - mLineEdit->setToolTip( parameterDefinition()->toolTip() ); - connect( mLineEdit, &QLineEdit::textChanged, this, [ = ] + mGeometryWidget = new QgsGeometryWidget(); + mGeometryWidget->setToolTip( parameterDefinition()->toolTip() ); + connect( mGeometryWidget, &QgsGeometryWidget::geometryValueChanged, this, [ = ]( const QgsReferencedGeometry & ) { emit widgetValueHasChanged( this ); } ); - return mLineEdit; + return mGeometryWidget; } } return nullptr; @@ -4476,22 +4479,31 @@ QWidget *QgsProcessingGeometryWidgetWrapper::createWidget() void QgsProcessingGeometryWidgetWrapper::setWidgetValue( const QVariant &value, QgsProcessingContext &context ) { - if ( mLineEdit ) + if ( mGeometryWidget ) { QgsGeometry g = QgsProcessingParameters::parameterAsGeometry( parameterDefinition(), value, context ); if ( !g.isNull() ) - mLineEdit->setText( g.asWkt() ); + { + mGeometryWidget->setGeometryValue( QgsReferencedGeometry( g, QgsCoordinateReferenceSystem() ) ); + } else - mLineEdit->clear(); + { + mGeometryWidget->clearGeometry(); + } } } QVariant QgsProcessingGeometryWidgetWrapper::widgetValue() const { - if ( mLineEdit ) - return mLineEdit->text().isEmpty() ? QVariant() : mLineEdit->text(); + if ( mGeometryWidget ) + { + const QgsReferencedGeometry geometry = mGeometryWidget->geometryValue(); + return geometry.isEmpty() ? QVariant() : geometry.asWkt(); + } else + { return QVariant(); + } } QStringList QgsProcessingGeometryWidgetWrapper::compatibleParameterTypes() const diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h index bb3052e2152..db31a36cf34 100644 --- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h +++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h @@ -75,6 +75,7 @@ class QgsProcessingPointCloudExpressionLineEdit; class QgsProcessingRasterCalculatorExpressionLineEdit; class QgsRubberBand; class QgsHighlightableLineEdit; +class QgsGeometryWidget; ///@cond PRIVATE @@ -1238,7 +1239,7 @@ class GUI_EXPORT QgsProcessingGeometryParameterDefinitionWidget : public QgsProc private: - QLineEdit *mDefaultLineEdit = nullptr; + QgsGeometryWidget *mGeometryWidget = nullptr; }; @@ -1274,7 +1275,7 @@ class GUI_EXPORT QgsProcessingGeometryWidgetWrapper : public QgsAbstractProcessi QString modelerExpressionFormatString() const override; private: - QLineEdit *mLineEdit = nullptr; + QgsGeometryWidget *mGeometryWidget = nullptr; friend class TestProcessingGui; }; diff --git a/src/gui/qgsadvanceddigitizingtools.cpp b/src/gui/qgsadvanceddigitizingtools.cpp index 00ef475180c..d57beda9421 100644 --- a/src/gui/qgsadvanceddigitizingtools.cpp +++ b/src/gui/qgsadvanceddigitizingtools.cpp @@ -73,6 +73,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle1X->setToolTip( tr( "X coordinate" ) ); mCircle1X->setMinimum( std::numeric_limits::lowest() ); mCircle1X->setMaximum( std::numeric_limits::max() ); + mCircle1X->setDecimals( mCadDockWidget->constraintX()->precision() ); mCircle1X->setClearValue( 0.0 ); connect( mCircle1X, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle1Digitize->setChecked( false ); } ); layout->addWidget( mCircle1X, 1, 1 ); @@ -84,6 +85,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle1Y->setToolTip( tr( "Y coordinate" ) ); mCircle1Y->setMinimum( std::numeric_limits::lowest() ); mCircle1Y->setMaximum( std::numeric_limits::max() ); + mCircle1Y->setDecimals( mCadDockWidget->constraintY()->precision() ); mCircle1Y->setClearValue( 0.0 ); connect( mCircle1Y, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle1Digitize->setChecked( false ); } ); layout->addWidget( mCircle1Y, 2, 1 ); @@ -122,6 +124,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle2X->setToolTip( tr( "X coordinate" ) ); mCircle2X->setMinimum( std::numeric_limits::lowest() ); mCircle2X->setMaximum( std::numeric_limits::max() ); + mCircle2X->setDecimals( mCadDockWidget->constraintX()->precision() ); mCircle2X->setClearValue( 0.0 ); connect( mCircle2X, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle2Digitize->setChecked( false ); } ); layout->addWidget( mCircle2X, 5, 1 ); @@ -133,6 +136,7 @@ QWidget *QgsAdvancedDigitizingCirclesIntersectionTool::createWidget() mCircle2Y->setToolTip( tr( "Y coordinate" ) ); mCircle2Y->setMinimum( std::numeric_limits::lowest() ); mCircle2Y->setMaximum( std::numeric_limits::max() ); + mCircle2Y->setDecimals( mCadDockWidget->constraintY()->precision() ); mCircle2Y->setClearValue( 0.0 ); connect( mCircle2Y, &QgsDoubleSpinBox::textEdited, this, [ = ]() { mCircle2Digitize->setChecked( false ); } ); layout->addWidget( mCircle2Y, 6, 1 ); diff --git a/src/gui/settings/qgssettingseditorwidgetregistry.cpp b/src/gui/settings/qgssettingseditorwidgetregistry.cpp index fd4872ae69d..39afcb47a42 100644 --- a/src/gui/settings/qgssettingseditorwidgetregistry.cpp +++ b/src/gui/settings/qgssettingseditorwidgetregistry.cpp @@ -81,6 +81,11 @@ bool QgsSettingsEditorWidgetRegistry::addWrapper( QgsSettingsEditorWidgetWrapper return true; } +void QgsSettingsEditorWidgetRegistry::addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper, const QgsSettingsEntryBase *setting ) +{ + mSpecificWrappers.insert( setting, wrapper ); +} + QgsSettingsEditorWidgetWrapper *QgsSettingsEditorWidgetRegistry::createWrapper( const QString &id, QObject *parent ) const { QgsSettingsEditorWidgetWrapper *wrapper = mWrappers.value( id ); @@ -97,6 +102,10 @@ QgsSettingsEditorWidgetWrapper *QgsSettingsEditorWidgetRegistry::createWrapper( QWidget *QgsSettingsEditorWidgetRegistry::createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent ) const { + if ( mSpecificWrappers.contains( setting ) ) + { + return mSpecificWrappers.value( setting )->createEditor( setting, dynamicKeyPartList, parent ); + } QgsSettingsEditorWidgetWrapper *eww = createWrapper( setting->typeId(), parent ); if ( eww ) return eww->createEditor( setting, dynamicKeyPartList, parent ); diff --git a/src/gui/settings/qgssettingseditorwidgetregistry.h b/src/gui/settings/qgssettingseditorwidgetregistry.h index 3eef4026679..d42be8b1230 100644 --- a/src/gui/settings/qgssettingseditorwidgetregistry.h +++ b/src/gui/settings/qgssettingseditorwidgetregistry.h @@ -45,14 +45,21 @@ class GUI_EXPORT QgsSettingsEditorWidgetRegistry */ bool addWrapper( QgsSettingsEditorWidgetWrapper *wrapper SIP_TRANSFER ); + /** + * Adds an editor widget \a wrapper for a specific setting to the registry + * \since QGIS 3.40 + */ + void addWrapperForSetting( QgsSettingsEditorWidgetWrapper *wrapper SIP_TRANSFER, const QgsSettingsEntryBase *setting SIP_KEEPREFERENCE ); + //! Returns a new instance of the editor widget for the given \a id - QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const; + QgsSettingsEditorWidgetWrapper *createWrapper( const QString &id, QObject *parent ) const SIP_FACTORY; //! Creates an editor widget for the given \a setting using the corresponding registered wrapper - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = nullptr ) const SIP_FACTORY; + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList, QWidget *parent = nullptr ) const SIP_TRANSFERBACK; private: QMap mWrappers; + QMap mSpecificWrappers; }; #endif // QGSSETTINGSEDITORREGISTRY_H diff --git a/src/gui/settings/qgssettingseditorwidgetwrapper.h b/src/gui/settings/qgssettingseditorwidgetwrapper.h index 70066075357..2561385cfc2 100644 --- a/src/gui/settings/qgssettingseditorwidgetwrapper.h +++ b/src/gui/settings/qgssettingseditorwidgetwrapper.h @@ -36,7 +36,7 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject Q_OBJECT public: //! Creates a wrapper from the definition stored in a \a widget created by createEditor() - static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ) SIP_FACTORY; + static QgsSettingsEditorWidgetWrapper *fromWidget( const QWidget *widget ); //! Constructor QgsSettingsEditorWidgetWrapper( QObject *parent = nullptr ); @@ -50,10 +50,10 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject virtual QString id() const = 0; //! Creates a new instance of the editor wrapper so it can be configured for a widget and a setting - virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = nullptr ) const = 0; + virtual QgsSettingsEditorWidgetWrapper *createWrapper( QObject *parent = nullptr ) const = 0 SIP_FACTORY; //! Creates the editor widget for the given \a setting - QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = nullptr ); + QWidget *createEditor( const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList(), QWidget *parent = nullptr ) SIP_TRANSFERBACK; //! Configures the \a editor according the setting bool configureEditor( QWidget *editor, const QgsSettingsEntryBase *setting, const QStringList &dynamicKeyPartList = QStringList() ); @@ -80,7 +80,7 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject * Sets the \a value of the widget * The wrapper must be configured before calling this medthod */ - virtual void setWidgetFromVariant( const QVariant &value ) const = 0; + virtual bool setWidgetFromVariant( const QVariant &value ) const = 0; /** * Configure the settings update behavior when a widget value is changed. @@ -103,10 +103,10 @@ class GUI_EXPORT QgsSettingsEditorWidgetWrapper : public QObject protected: //! Creates the widgets - virtual QWidget *createEditorPrivate( QWidget *parent = nullptr ) const = 0; + virtual QWidget *createEditorPrivate( QWidget *parent = nullptr ) const = 0 SIP_TRANSFERBACK; //! Configures an existing \a editor widget - virtual bool configureEditorPrivate( QWidget *editor, const QgsSettingsEntryBase *setting ) = 0; + virtual bool configureEditorPrivate( QWidget *editor SIP_TRANSFERBACK, const QgsSettingsEntryBase *setting SIP_KEEPREFERENCE ) = 0; /** * Enables automatic update, which causes the setting to be updated immediately when the widget diff --git a/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h b/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h index d5d93de353a..acf1d3819f0 100644 --- a/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h +++ b/src/gui/settings/qgssettingseditorwidgetwrapperimpl.h @@ -63,9 +63,9 @@ class QgsSettingsEditorWidgetWrapperTemplate : public QgsSettingsEditorWidgetWra virtual bool setSettingFromWidget() const override = 0; - void setWidgetFromVariant( const QVariant &value ) const override + bool setWidgetFromVariant( const QVariant &value ) const override { - setWidgetValue( mSetting->convertFromVariant( value ) ); + return setWidgetValue( mSetting->convertFromVariant( value ) ); } //! Sets the widget value diff --git a/src/gui/settings/qgssettingstreemodel.cpp b/src/gui/settings/qgssettingstreemodel.cpp index 450a7c6d881..c3a60a105d2 100644 --- a/src/gui/settings/qgssettingstreemodel.cpp +++ b/src/gui/settings/qgssettingstreemodel.cpp @@ -486,7 +486,10 @@ void QgsSettingsTreeItemDelegate::setEditorData( QWidget *editor, const QModelIn { QgsSettingsEditorWidgetWrapper *eww = QgsSettingsEditorWidgetWrapper::fromWidget( editor ); if ( eww ) - eww->setWidgetFromVariant( index.model()->data( index, Qt::DisplayRole ) ); + { + const QVariant value = index.model()->data( index, Qt::DisplayRole ); + eww->setWidgetFromVariant( value ); + } } void QgsSettingsTreeItemDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index ) const diff --git a/src/gui/stac/qgsstacdataitemguiprovider.cpp b/src/gui/stac/qgsstacdataitemguiprovider.cpp index 7d709bee69c..c4ae1db25c5 100644 --- a/src/gui/stac/qgsstacdataitemguiprovider.cpp +++ b/src/gui/stac/qgsstacdataitemguiprovider.cpp @@ -217,9 +217,9 @@ void QgsStacDataItemGuiProvider::downloadAssets( QgsDataItem *item, QgsDataItemG connect( fetcher, &QgsNetworkContentFetcherTask::fetched, item, [fetcher, folder, context] { QNetworkReply *reply = fetcher->reply(); - if ( !reply ) + if ( !reply || reply->error() != QNetworkReply::NoError ) { - // canceled + // canceled or failed return; } else diff --git a/src/providers/postgres/qgspostgresexpressioncompiler.cpp b/src/providers/postgres/qgspostgresexpressioncompiler.cpp index f24fa7334f2..23d797c2e29 100644 --- a/src/providers/postgres/qgspostgresexpressioncompiler.cpp +++ b/src/providers/postgres/qgspostgresexpressioncompiler.cpp @@ -14,6 +14,7 @@ ***************************************************************************/ #include "qgspostgresexpressioncompiler.h" +#include "qgsexpressionutils.h" #include "qgssqlexpressioncompiler.h" #include "qgsexpressionnodeimpl.h" @@ -46,13 +47,10 @@ QString QgsPostgresExpressionCompiler::quotedValue( const QVariant &value, bool default: - if ( value.userType() == qMetaTypeId() ) - { - const QgsGeometry geom = value.value(); - return QString( "ST_GeomFromText('%1',%2)" ).arg( geom.asWkt() ).arg( mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid ); - } - break; - + QgsGeometry geom = QgsExpressionUtils::getGeometry( value, nullptr ); + if ( geom.isNull() ) + break; + return QString( "ST_GeomFromText('%1',%2)" ).arg( geom.asWkt() ).arg( mRequestedSrid.isEmpty() ? mDetectedSrid : mRequestedSrid ); } return QgsPostgresConn::quotedValue( value ); diff --git a/src/ui/layout/qgslayoutimageexportoptions.ui b/src/ui/layout/qgslayoutimageexportoptions.ui index c5605c61515..6e230a3b8b9 100644 --- a/src/ui/layout/qgslayoutimageexportoptions.ui +++ b/src/ui/layout/qgslayoutimageexportoptions.ui @@ -20,20 +20,33 @@ Export Options - - + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + If checked, a separate world file which georeferences exported images will be created + - Page height + Generate world file - - - - If unchecked, the generated images will not be antialiased - + + - Enable antialiasing + Page width @@ -54,33 +67,27 @@ 99999999 - + false - - - - If checked, a separate world file which georeferences exported images will be created - + + - Generate world file + Export resolution - - - - Qt::Horizontal + + + + If unchecked, the generated images will not be antialiased - - - 40 - 20 - + + Enable antialiasing - + @@ -93,15 +100,15 @@ 3000 - + false - - + + - Page width + Page height @@ -122,15 +129,47 @@ 99999999 - + false - - + + - Export resolution + Quality + + + + + + + 1 + + + 100 + + + 90 + + + Qt::Horizontal + + + + + + + % + + + 1 + + + 100 + + + 90 @@ -330,6 +369,8 @@ mResolutionSpinBox mWidthSpinBox mHeightSpinBox + mQualitySlider + mQualitySpinBox mAntialiasingCheckBox mGenerateWorldFile mClipToContentGroupBox @@ -337,6 +378,7 @@ mLeftMarginSpinBox mRightMarginSpinBox mBottomMarginSpinBox + mOpenAfterExportingCheckBox diff --git a/src/ui/layout/qgslayoutpagepropertieswidget.ui b/src/ui/layout/qgslayoutpagepropertieswidget.ui index 9032c17902d..8e0938dd06f 100644 --- a/src/ui/layout/qgslayoutpagepropertieswidget.ui +++ b/src/ui/layout/qgslayoutpagepropertieswidget.ui @@ -6,7 +6,7 @@ 0 0 - 385 + 407 405 @@ -86,7 +86,7 @@ 100.000000000000000 - + false @@ -105,7 +105,7 @@ 100.000000000000000 - + false @@ -213,20 +213,14 @@ - - - - Qt::Vertical + + + + Background - - - 20 - 40 - - - + - + @@ -263,14 +257,7 @@ - - - - Background - - - - + true @@ -292,6 +279,29 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Resets all other pages' size and background color to match this page + + + Apply to all Pages + + + @@ -338,7 +348,6 @@ mSizeUnitsComboBox mExcludePageCheckBox mExcludePageDDBtn - mSymbolButton diff --git a/tests/src/3d/testqgs3dcameracontroller.cpp b/tests/src/3d/testqgs3dcameracontroller.cpp index b3dc625bff7..1ebc68484c0 100644 --- a/tests/src/3d/testqgs3dcameracontroller.cpp +++ b/tests/src/3d/testqgs3dcameracontroller.cpp @@ -151,9 +151,9 @@ void TestQgs3DCameraController::testTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); QVector3D diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QVector3D diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); @@ -667,9 +667,9 @@ void TestQgs3DCameraController::testTranslateRotationCenterTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); QVector3D diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QVector3D diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); @@ -702,9 +702,9 @@ void TestQgs3DCameraController::testTranslateRotationCenterTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::RotationCenter ); diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 9.1, 8.2, -42.0 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 9.1, 8.2, -42.0 ), 4.0 ); diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( 3.8, 5.3, 68.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( 3.8, 5.3, 68.3 ), 4.0 ); float diffPitch = scene->cameraController()->pitch() - initialPitch; float diffYaw = scene->cameraController()->yaw() - initialYaw; QGSCOMPARENEAR( diffPitch, 2.5, 0.1 ); @@ -748,9 +748,9 @@ void TestQgs3DCameraController::testTranslateRotationCenterTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -20.2, 0.0, -22.4 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -20.2, 0.0, -22.4 ), 4.0 ); diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -20.2, 0.0, -22.4 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -20.2, 0.0, -22.4 ), 4.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); @@ -829,9 +829,9 @@ void TestQgs3DCameraController::testTranslateZoomWheelTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); QVector3D diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QVector3D diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); @@ -857,7 +857,7 @@ void TestQgs3DCameraController::testTranslateZoomWheelTranslate() scene->cameraController()->depthBufferCaptured( depthImage ); QGSCOMPARENEARVECTOR3D( scene->cameraController()->mZoomPoint, QVector3D( 4.8, 9.9, 4.4 ), 1.0 ); - QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -615.0, -108.0, -116.6 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( scene->cameraController()->cameraPose().centerPoint(), QVector3D( -615.0, -108.0, -116.6 ), 4.0 ); QGSCOMPARENEAR( scene->cameraController()->cameraPose().distanceFromCenterPoint(), 1743.4, 2.0 ); QCOMPARE( scene->cameraController()->mCumulatedWheelY, 0 ); QCOMPARE( scene->cameraController()->mClickPoint, QPoint() ); @@ -982,9 +982,9 @@ void TestQgs3DCameraController::testTranslateRotationCameraTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); QVector3D diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QVector3D diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -944.6, 0.0, -180.3 ), 4.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); @@ -1015,9 +1015,9 @@ void TestQgs3DCameraController::testTranslateRotationCameraTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::RotationCamera ); diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 2.7, 1.5, -78.0 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( 2.7, 1.5, -78.0 ), 2.0 ); diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( 0.0, 0.0, 0.0 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( 0.0, 0.0, 0.0 ), 2.0 ); float diffPitch = scene->cameraController()->pitch() - initialPitch; float diffYaw = scene->cameraController()->yaw() - initialYaw; QGSCOMPARENEAR( diffPitch, 1.8, 0.1 ); @@ -1061,9 +1061,9 @@ void TestQgs3DCameraController::testTranslateRotationCameraTranslate() QCOMPARE( scene->cameraController()->mCurrentOperation, QgsCameraController::MouseOperation::Translation ); diffViewCenter = scene->cameraController()->camera()->viewCenter() - initialCamViewCenter; - QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -20.2, 0.0, -22.4 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffViewCenter, QVector3D( -20.2, 0.0, -22.4 ), 2.0 ); diffPosition = scene->cameraController()->camera()->position() - initialCamPosition; - QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -20.2, 0.0, -22.4 ), 1.0 ); + QGSCOMPARENEARVECTOR3D( diffPosition, QVector3D( -20.2, 0.0, -22.4 ), 2.0 ); QCOMPARE( scene->cameraController()->pitch(), initialPitch ); QCOMPARE( scene->cameraController()->yaw(), initialYaw ); diff --git a/tests/src/3d/testqgs3drendering.cpp b/tests/src/3d/testqgs3drendering.cpp index 79bd8d8e071..4998808d301 100644 --- a/tests/src/3d/testqgs3drendering.cpp +++ b/tests/src/3d/testqgs3drendering.cpp @@ -554,6 +554,7 @@ void TestQgs3DRendering::testExtrudedPolygonsTexturedPhong() materialSettings.setSpecular( QColor( 10, 10, 10 ) ); materialSettings.setShininess( 1.0 ); materialSettings.setDiffuseTexturePath( testDataPath( "/sample_image.png" ) ); + materialSettings.setTextureScale( 0.05 ); QgsPolygon3DSymbol *symbol3d = new QgsPolygon3DSymbol; symbol3d->setMaterialSettings( materialSettings.clone() ); symbol3d->setExtrusionHeight( 10.f ); @@ -869,7 +870,7 @@ void TestQgs3DRendering::testExtrudedPolygonsGoochShading() delete scene; delete map; - QGSVERIFYIMAGECHECK( "polygon3d_extrusion_gooch_shading", "polygon3d_extrusion_gooch_shading", img, QString(), 40, QSize( 0, 0 ), 2 ); + QGSVERIFYIMAGECHECK( "polygon3d_extrusion_gooch_shading", "polygon3d_extrusion_gooch_shading", img, QString(), 50, QSize( 0, 0 ), 2 ); } void TestQgs3DRendering::testExtrudedPolygonsMetalRoughShading() diff --git a/tests/src/app/testqgsidentify.cpp b/tests/src/app/testqgsidentify.cpp index 951326b116a..58392b46e1a 100644 --- a/tests/src/app/testqgsidentify.cpp +++ b/tests/src/app/testqgsidentify.cpp @@ -72,6 +72,7 @@ class TestQgsIdentify : public QObject void testLineStringZ(); void testPolygonZ(); void identifyPointCloud(); + void identifyVirtualPointCloud(); private: void doAction(); @@ -1303,6 +1304,34 @@ void TestQgsIdentify::identifyPointCloud() #endif } +void TestQgsIdentify::identifyVirtualPointCloud() +{ +#ifdef HAVE_COPC + std::unique_ptr< QgsPointCloudLayer > pointCloud = std::make_unique< QgsPointCloudLayer >( QStringLiteral( TEST_DATA_DIR ) + "/point_clouds/virtual/sunshine-coast/combined.vpc", QStringLiteral( "pointcloud" ), QStringLiteral( "vpc" ) ); + QVERIFY( pointCloud->isValid() ); + pointCloud->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) ); + QCOMPARE( pointCloud->crs3D().horizontalCrs().authid(), QStringLiteral( "EPSG:28356" ) ); + + for ( int i = 0; i < pointCloud->dataProvider()->subIndexes().size(); i++ ) + pointCloud->dataProvider()->loadSubIndex( i ); + + // set project CRS and ellipsoid + // Note that using a different CRS here (a world-wide WGS84-based one) caused + // problems on some machines due to insufficient precision in reprojection. + QgsProject::instance()->setCrs( pointCloud->crs() ); + canvas->setDestinationCrs( pointCloud->crs() ); + canvas->setExtent( QgsRectangle::fromCenterAndSize( QgsPointXY( 498065.5, 7050992.5 ), 1, 1 ) ); + + const QgsPointXY mapPoint = canvas->getCoordinateTransform()->transform( 498065.23, 7050992.90 ); + + std::unique_ptr< QgsMapToolIdentifyAction > action( new QgsMapToolIdentifyAction( canvas ) ); + QList result = action->identify( static_cast< int >( mapPoint.x() ), static_cast< int >( mapPoint.y() ), QList() << pointCloud.get() ); + QCOMPARE( result.length(), 1 ); + double z = result.at( 0 ).mDerivedAttributes[ QStringLiteral( "Z" )].toDouble(); + QGSCOMPARENEAR( z, 74.91, 0.001 ); +#endif +} + QGSTEST_MAIN( TestQgsIdentify ) #include "testqgsidentify.moc" diff --git a/tests/src/core/geometry/testqgsgeometry.cpp b/tests/src/core/geometry/testqgsgeometry.cpp index 82c0643caee..74c06901552 100644 --- a/tests/src/core/geometry/testqgsgeometry.cpp +++ b/tests/src/core/geometry/testqgsgeometry.cpp @@ -89,6 +89,7 @@ class TestQgsGeometry : public QgsTest void curveIndexOf(); void splitCurve_data(); void splitCurve(); + void splitToDisjointXYParts(); void fromBox3d(); void fromPoint(); @@ -752,6 +753,38 @@ void TestQgsGeometry::splitCurve() QCOMPARE( p2->asWkt(), curve2 ); } +void TestQgsGeometry::splitToDisjointXYParts() +{ + QgsLineString onePartLine( QgsPoint( 1.0, 1.0 ), QgsPoint( 2.0, 2.0 ) ); + QVector onePartParts = onePartLine.splitToDisjointXYParts(); + QCOMPARE( onePartParts.size(), 1 ); + QCOMPARE( onePartParts[0]->asWkt(), onePartLine.asWkt() ); + for ( QgsLineString *part : onePartParts ) + delete part; + + QgsLineString onePointLine( QVector {QgsPoint( 1.0, 1.0 )} ); + QVector onePointParts = onePointLine.splitToDisjointXYParts(); + QCOMPARE( onePointParts.size(), 1 ); + QCOMPARE( onePointParts[0]->asWkt(), onePointLine.asWkt() ); + for ( QgsLineString *part : onePointParts ) + delete part; + + QgsLineString emptyLine( QVector { } ); + QVector emptyParts = emptyLine.splitToDisjointXYParts(); + QCOMPARE( emptyParts.size(), 1 ); + QCOMPARE( emptyParts[0]->asWkt(), emptyLine.asWkt() ); + for ( QgsLineString *part : emptyParts ) + delete part; + + QgsLineString triangle( QVector {QgsPoint( 0.0, 0.0 ), QgsPoint( 1.0, 0.0 ), QgsPoint( 1.0, 1.0 ), QgsPoint( 0.0, 0.0 )} ); + QVector triangleParts = triangle.splitToDisjointXYParts(); + QCOMPARE( triangleParts.size(), 2 ); + QCOMPARE( triangleParts[0]->asWkt(), "LineString (0 0, 1 0, 1 1)" ); + QCOMPARE( triangleParts[1]->asWkt(), "LineString (1 1, 0 0)" ); + for ( QgsLineString *part : triangleParts ) + delete part; +} + void TestQgsGeometry::fromBox3d() { QgsBox3D box3d( 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 ); diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 8a301bdaae8..3fd0188f861 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -3423,12 +3423,16 @@ class TestQgsExpression: public QObject QgsPolylineXY line; line << QgsPointXY( 1, 1 ) << QgsPointXY( 4, 2 ) << QgsPointXY( 3, 1 ); + QgsGeometry lineGeometry = QgsGeometry::fromPolylineXY( line ); QTest::newRow( "geom x" ) << "$x" << QgsGeometry( std::make_unique( point ) ) << false << QVariant( 123. ); QTest::newRow( "geom y" ) << "$y" << QgsGeometry( std::make_unique( point ) ) << false << QVariant( 456. ); QTest::newRow( "geom z" ) << "$z" << QgsGeometry( std::make_unique( point ) ) << false << QVariant( 789. ); - QTest::newRow( "geom xat" ) << "xat(-1)" << QgsGeometry::fromPolylineXY( line ) << false << QVariant( 3. ); - QTest::newRow( "geom yat" ) << "yat(1)" << QgsGeometry::fromPolylineXY( line ) << false << QVariant( 2. ); + QTest::newRow( "geom xat" ) << "xat(-1)" << lineGeometry << false << QVariant( 3. ); + QTest::newRow( "geom yat" ) << "yat(1)" << lineGeometry << false << QVariant( 2. ); + QTest::newRow( "geom length" ) << "length($geometry)" << lineGeometry << false << QVariant( lineGeometry.length() ); + QTest::newRow( "collected geometry" ) << "geom_to_wkt(collect_geometries($geometry, $geometry))" << lineGeometry << false << QVariant( QStringLiteral( "MultiLineString ((1 1, 4 2, 3 1),(1 1, 4 2, 3 1))" ) ); QTest::newRow( "null geometry" ) << "$geometry" << QgsGeometry() << false << QVariant(); + QTest::newRow( "empty geometry" ) << "geom_to_wkt($geometry)" << QgsGeometry().fromWkt( QStringLiteral( "Point()" ) ) << false << QVariant( QStringLiteral( "Point EMPTY" ) ); } void eval_geometry() @@ -3439,16 +3443,34 @@ class TestQgsExpression: public QObject QFETCH( QVariant, result ); QgsFeature f; - f.setGeometry( geom ); + QList geometryList; + geometryList << geom << QgsReferencedGeometry( geom, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) ); - QgsExpression exp( string ); - QCOMPARE( exp.hasParserError(), false ); - QCOMPARE( exp.needsGeometry(), true ); + // With standard geometry + { + f.setGeometry( geom ); + QgsExpression exp( string ); + QCOMPARE( exp.hasParserError(), false ); + QCOMPARE( exp.needsGeometry(), true ); - QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); - QVariant out = exp.evaluate( &context ); - QCOMPARE( exp.hasEvalError(), evalError ); - QCOMPARE( out, result ); + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + QVariant out = exp.evaluate( &context ); + QCOMPARE( exp.hasEvalError(), evalError ); + QCOMPARE( out, result ); + } + + // With referenced geometry + { + f.setGeometry( QgsReferencedGeometry( geom, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ) ) ); + QgsExpression exp( string ); + QCOMPARE( exp.hasParserError(), false ); + QCOMPARE( exp.needsGeometry(), true ); + + QgsExpressionContext context = QgsExpressionContextUtils::createFeatureBasedContext( f, QgsFields() ); + QVariant out = exp.evaluate( &context ); + QCOMPARE( exp.hasEvalError(), evalError ); + QCOMPARE( out, result ); + } } void testGeometryFromContext() diff --git a/tests/src/core/testqgsstac.cpp b/tests/src/core/testqgsstac.cpp index 1bfa570aa7f..d2b834dfab7 100644 --- a/tests/src/core/testqgsstac.cpp +++ b/tests/src/core/testqgsstac.cpp @@ -24,6 +24,8 @@ #include "qgsstaccatalog.h" #include "qgsstaccollection.h" #include "qgsstacitem.h" +#include "qgsstacitemcollection.h" +#include "qgsstaccollections.h" #include "qgsapplication.h" #include "qgsproject.h" #include "qgis.h" @@ -48,6 +50,8 @@ class TestQgsStac : public QObject void testParseLocalCatalog(); void testParseLocalCollection(); void testParseLocalItem(); + void testParseLocalItemCollection(); + void testParseLocalCollections(); private: QString mDataDir; @@ -69,9 +73,9 @@ void TestQgsStac::cleanupTestCase() void TestQgsStac::testParseLocalCatalog() { - const QString fileName = mDataDir + QStringLiteral( "catalog.json" ); + const QUrl url( QStringLiteral( "file://%1%2" ).arg( mDataDir, QStringLiteral( "catalog.json" ) ) ); QgsStacController c; - QgsStacObject *obj = c.fetchStacObject( QStringLiteral( "file://%1" ).arg( fileName ) ); + QgsStacObject *obj = c.fetchStacObject( url.toString() ); QVERIFY( obj ); QCOMPARE( obj->type(), QgsStacObject::Type::Catalog ); QgsStacCatalog *cat = dynamic_cast< QgsStacCatalog * >( obj ); @@ -81,17 +85,27 @@ void TestQgsStac::testParseLocalCatalog() QCOMPARE( cat->stacVersion(), QLatin1String( "1.0.0" ) ); QCOMPARE( cat->title(), QLatin1String( "Example Catalog" ) ); QCOMPARE( cat->description(), QLatin1String( "This catalog is a simple demonstration of an example catalog that is used to organize a hierarchy of collections and their items." ) ); - QCOMPARE( cat->links().size(), 6 ); QVERIFY( cat->stacExtensions().isEmpty() ); + // check that relative links are correctly resolved into absolute links + const QVector links = cat->links(); + QCOMPARE( links.size(), 6 ); + const QString basePath = url.adjusted( QUrl::RemoveFilename ).toString(); + QCOMPARE( links.at( 0 ).href(), QStringLiteral( "%1catalog.json" ).arg( basePath ) ); + QCOMPARE( links.at( 1 ).href(), QStringLiteral( "%1extensions-collection/collection.json" ).arg( basePath ) ); + QCOMPARE( links.at( 2 ).href(), QStringLiteral( "%1collection-only/collection.json" ).arg( basePath ) ); + QCOMPARE( links.at( 3 ).href(), QStringLiteral( "%1collection-only/collection-with-schemas.json" ).arg( basePath ) ); + QCOMPARE( links.at( 4 ).href(), QStringLiteral( "%1collectionless-item.json" ).arg( basePath ) ); + QCOMPARE( links.at( 5 ).href(), QStringLiteral( "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/catalog.json" ) ); + delete cat; } void TestQgsStac::testParseLocalCollection() { - const QString fileName = mDataDir + QStringLiteral( "collection.json" ); + const QUrl url( QStringLiteral( "file://%1%2" ).arg( mDataDir, QStringLiteral( "collection.json" ) ) ); QgsStacController c; - QgsStacObject *obj = c.fetchStacObject( QStringLiteral( "file://%1" ).arg( fileName ) ); + QgsStacObject *obj = c.fetchStacObject( url.toString() ); QVERIFY( obj ); QCOMPARE( obj->type(), QgsStacObject::Type::Collection ); QgsStacCollection *col = dynamic_cast< QgsStacCollection * >( obj ); @@ -101,7 +115,17 @@ void TestQgsStac::testParseLocalCollection() QCOMPARE( col->stacVersion(), QLatin1String( "1.0.0" ) ); QCOMPARE( col->title(), QLatin1String( "Simple Example Collection" ) ); QCOMPARE( col->description(), QLatin1String( "A simple collection demonstrating core catalog fields with links to a couple of items" ) ); - QCOMPARE( col->links().size(), 5 ); + + // check that relative links are correctly resolved into absolute links + const QVector links = col->links(); + QCOMPARE( links.size(), 5 ); + const QString basePath = url.adjusted( QUrl::RemoveFilename ).toString(); + QCOMPARE( links.at( 0 ).href(), QStringLiteral( "%1collection.json" ).arg( basePath ) ); + QCOMPARE( links.at( 1 ).href(), QStringLiteral( "%1simple-item.json" ).arg( basePath ) ); + QCOMPARE( links.at( 2 ).href(), QStringLiteral( "%1core-item.json" ).arg( basePath ) ); + QCOMPARE( links.at( 3 ).href(), QStringLiteral( "%1extended-item.json" ).arg( basePath ) ); + QCOMPARE( links.at( 4 ).href(), QStringLiteral( "https://raw.githubusercontent.com/radiantearth/stac-spec/v1.0.0/examples/collection.json" ) ); + QCOMPARE( col->providers().size(), 1 ); QCOMPARE( col->stacExtensions().size(), 3 ); QCOMPARE( col->license(), QLatin1String( "CC-BY-4.0" ) ); @@ -129,9 +153,9 @@ void TestQgsStac::testParseLocalCollection() void TestQgsStac::testParseLocalItem() { - const QString fileName = mDataDir + QStringLiteral( "core-item.json" ); + const QUrl url( QStringLiteral( "file://%1%2" ).arg( mDataDir, QStringLiteral( "core-item.json" ) ) ); QgsStacController c; - QgsStacObject *obj = c.fetchStacObject( QStringLiteral( "file://%1" ).arg( fileName ) ); + QgsStacObject *obj = c.fetchStacObject( url.toString() ); QVERIFY( obj ); QCOMPARE( obj->type(), QgsStacObject::Type::Item ); QgsStacItem *item = dynamic_cast( obj ); @@ -139,12 +163,71 @@ void TestQgsStac::testParseLocalItem() QVERIFY( item ); QCOMPARE( item->id(), QLatin1String( "20201211_223832_CS2" ) ); QCOMPARE( item->stacVersion(), QLatin1String( "1.0.0" ) ); - QCOMPARE( item->links().size(), 4 ); QCOMPARE( item->stacExtensions().size(), 0 ); + + // check that relative links are correctly resolved into absolute links + const QVector links = item->links(); + QCOMPARE( links.size(), 4 ); + const QString basePath = url.adjusted( QUrl::RemoveFilename ).toString(); + QCOMPARE( links.at( 0 ).href(), QStringLiteral( "%1collection.json" ).arg( basePath ) ); + QCOMPARE( links.at( 1 ).href(), QStringLiteral( "%1collection.json" ).arg( basePath ) ); + QCOMPARE( links.at( 2 ).href(), QStringLiteral( "%1collection.json" ).arg( basePath ) ); + QCOMPARE( links.at( 3 ).href(), QStringLiteral( "http://remotedata.io/catalog/20201211_223832_CS2/index.html" ) ); + QCOMPARE( item->assets().size(), 6 ); + QgsStacAsset asset = item->assets().value( QStringLiteral( "analytic" ), QgsStacAsset( {}, {}, {}, {}, {} ) ); + QCOMPARE( asset.href(), basePath + QStringLiteral( "20201211_223832_CS2_analytic.tif" ) ); + + asset = item->assets().value( QStringLiteral( "thumbnail" ), QgsStacAsset( {}, {}, {}, {}, {} ) ); + QCOMPARE( asset.href(), QStringLiteral( "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2.jpg" ) ); delete item; } +void TestQgsStac::testParseLocalItemCollection() +{ + const QString fileName = mDataDir + QStringLiteral( "itemcollection-sample-full.json" ); + QgsStacController c; + QgsStacItemCollection *ic = c.fetchItemCollection( QStringLiteral( "file://%1" ).arg( fileName ) ); + QVERIFY( ic ); + QCOMPARE( ic->numberReturned(), 1 ); + QCOMPARE( ic->numberMatched(), 10 ); + QCOMPARE( ic->rootUrl().toString(), QLatin1String( "http://stac.example.com/" ) ); + + QVector items = ic->items(); + QCOMPARE( items.size(), 1 ); + QCOMPARE( items.first()->id(), QLatin1String( "cs3-20160503_132131_05" ) ); + QCOMPARE( items.first()->stacVersion(), QLatin1String( "1.0.0" ) ); + QCOMPARE( items.first()->links().size(), 3 ); + QCOMPARE( items.first()->stacExtensions().size(), 0 ); + QCOMPARE( items.first()->assets().size(), 2 ); + + delete ic; +} + +void TestQgsStac::testParseLocalCollections() +{ + const QString fileName = mDataDir + QStringLiteral( "collectioncollection-sample-full.json" ); + QgsStacController c; + QgsStacCollections *cols = c.fetchCollections( QStringLiteral( "file://%1" ).arg( fileName ) ); + QVERIFY( cols ); + QCOMPARE( cols->numberReturned(), 1 ); + QCOMPARE( cols->numberMatched(), 11 ); + QCOMPARE( cols->rootUrl().toString(), QLatin1String( "http://stac.example.com/" ) ); + QCOMPARE( cols->url().toString(), QLatin1String( "http://stac.example.com/collections?page=2" ) ); + QCOMPARE( cols->nextUrl().toString(), QLatin1String( "http://stac.example.com/collections?page=3" ) ); + QCOMPARE( cols->prevUrl().toString(), QLatin1String( "http://stac.example.com/collections?page=1" ) ); + + QCOMPARE( cols->collections().size(), 1 ); + + QgsStacCollection *col = cols->collections().first(); + QCOMPARE( col->id(), QStringLiteral( "simple-collection" ) ); + QCOMPARE( col->stacVersion(), QLatin1String( "1.0.0" ) ); + QCOMPARE( col->links().size(), 3 ); + QCOMPARE( col->stacExtensions().size(), 0 ); + + delete cols; +} + QGSTEST_MAIN( TestQgsStac ) #include "testqgsstac.moc" diff --git a/tests/src/gui/testprocessinggui.cpp b/tests/src/gui/testprocessinggui.cpp index 262291feb43..ad5dbdcc9d0 100644 --- a/tests/src/gui/testprocessinggui.cpp +++ b/tests/src/gui/testprocessinggui.cpp @@ -105,6 +105,7 @@ #include "qgsprocessingalignrasterlayerswidgetwrapper.h" #include "qgsprocessingrasteroptionswidgetwrapper.h" #include "qgsrasterformatsaveoptionswidget.h" +#include "qgsgeometrywidget.h" class TestParamType : public QgsProcessingParameterDefinition @@ -6069,15 +6070,15 @@ void TestProcessingGui::testGeometryWrapper() QgsProcessingContext context; QWidget *w = wrapper.createWrappedWidget( context ); - QSignalSpy spy( &wrapper, &QgsProcessingLayoutItemWidgetWrapper::widgetValueHasChanged ); + QSignalSpy spy( &wrapper, &QgsProcessingGeometryWidgetWrapper::widgetValueHasChanged ); wrapper.setWidgetValue( QStringLiteral( "POINT (1 2)" ), context ); QCOMPARE( spy.count(), 1 ); QCOMPARE( wrapper.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); - QCOMPARE( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ).toLower() ); + QCOMPARE( static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->geometryValue().asWkt().toLower(), QStringLiteral( "point (1 2)" ).toLower() ); wrapper.setWidgetValue( QString(), context ); QCOMPARE( spy.count(), 2 ); QVERIFY( wrapper.widgetValue().toString().isEmpty() ); - QVERIFY( static_cast< QLineEdit * >( wrapper.wrappedWidget() )->text().isEmpty() ); + QVERIFY( static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->geometryValue().asWkt().isEmpty() ); QLabel *l = wrapper.createWrappedLabel(); if ( wrapper.type() != QgsProcessingGui::Batch ) @@ -6093,9 +6094,9 @@ void TestProcessingGui::testGeometryWrapper() } // check signal - static_cast< QLineEdit * >( wrapper.wrappedWidget() )->setText( QStringLiteral( "b" ) ); + static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->setGeometryValue( QgsReferencedGeometry( QgsGeometry::fromWkt( "point(0 0)" ), QgsCoordinateReferenceSystem() ) ); QCOMPARE( spy.count(), 3 ); - static_cast< QLineEdit * >( wrapper.wrappedWidget() )->clear(); + static_cast< QgsGeometryWidget * >( wrapper.wrappedWidget() )->clearGeometry(); QCOMPARE( spy.count(), 4 ); delete w; @@ -6112,19 +6113,19 @@ void TestProcessingGui::testGeometryWrapper() wrapper2.setWidgetValue( "POINT (1 2)", context ); QCOMPARE( spy2.count(), 1 ); QCOMPARE( wrapper2.widgetValue().toString().toLower(), QStringLiteral( "point (1 2)" ) ); - QCOMPARE( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().toLower(), QStringLiteral( "point (1 2)" ) ); + QCOMPARE( static_cast< QgsGeometryWidget * >( wrapper2.wrappedWidget() )->geometryValue().asWkt().toLower(), QStringLiteral( "point (1 2)" ) ); wrapper2.setWidgetValue( QVariant(), context ); QCOMPARE( spy2.count(), 2 ); QVERIFY( !wrapper2.widgetValue().isValid() ); - QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() ); + QVERIFY( static_cast< QgsGeometryWidget * >( wrapper2.wrappedWidget() )->geometryValue().asWkt().isEmpty() ); wrapper2.setWidgetValue( "POINT (1 3)", context ); QCOMPARE( spy2.count(), 3 ); wrapper2.setWidgetValue( "", context ); QCOMPARE( spy2.count(), 4 ); QVERIFY( !wrapper2.widgetValue().isValid() ); - QVERIFY( static_cast< QLineEdit * >( wrapper2.wrappedWidget() )->text().isEmpty() ); + QVERIFY( static_cast< QgsGeometryWidget * >( wrapper2.wrappedWidget() )->geometryValue().asWkt().isEmpty() ); delete w; }; diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index ce1dde961d8..16aa32a3f77 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -520,6 +520,7 @@ if (WITH_GUI) ADD_PYTHON_TEST(PyQgsSymbolButton test_qgssymbolbutton.py) ADD_PYTHON_TEST(PyQgsTabWidget test_qgstabwidget.py) ADD_PYTHON_TEST(PyQgsTextFormatWidget test_qgstextformatwidget.py) + ADD_PYTHON_TEST(PyQgsTextFormat test_qgstextformat.py) ADD_PYTHON_TEST(PyQgsTreeWidgetItem test_qgstreewidgetitem.py) ADD_PYTHON_TEST(PyQgsValidityResultsWidget test_qgsvalidityresultswidget.py) ADD_PYTHON_TEST(PyQgsVectorLayer test_qgsvectorlayer.py) diff --git a/tests/src/python/test_provider_ogr.py b/tests/src/python/test_provider_ogr.py index bea3c46863c..e03ee1ca34a 100644 --- a/tests/src/python/test_provider_ogr.py +++ b/tests/src/python/test_provider_ogr.py @@ -1626,6 +1626,15 @@ class PyQgsOGRProvider(QgisTestCase): encodedUri = QgsProviderRegistry.instance().encodeUri('ogr', parts) self.assertEqual(encodedUri, uri) + @unittest.skipIf(gdal.GetDriverByName("GTFS") is None, "GTFS driver required") + def testDecodeGTFS(self): + """Test querySublayers() for GTFS .zip dataset""" + + uri = os.path.join(TEST_DATA_DIR, "ogr", "gtfs_extract.zip") + metadata = QgsProviderRegistry.instance().providerMetadata('ogr') + res = metadata.querySublayers(uri) + self.assertEqual(len(res), 9) + def testDecodeEncodeUriCredentialOptions(self): """Test decodeUri/encodeUri credential options support""" diff --git a/tests/src/python/test_qgsbox3d.py b/tests/src/python/test_qgsbox3d.py index fd014c51c67..f1bd9ec936c 100644 --- a/tests/src/python/test_qgsbox3d.py +++ b/tests/src/python/test_qgsbox3d.py @@ -95,6 +95,31 @@ class TestQgsBox3d(unittest.TestCase): self.assertEqual(box.yMaximum(), 13.0) self.assertEqual(box.zMaximum(), 5.0) + # constructor using two corners + box = QgsBox3d(QgsVector3D(3, 4, 5), QgsVector3D(8, 9, 10)) + self.assertEqual(box.xMinimum(), 3.0) + self.assertEqual(box.yMinimum(), 4.0) + self.assertEqual(box.zMinimum(), 5.0) + self.assertEqual(box.xMaximum(), 8.0) + self.assertEqual(box.yMaximum(), 9.0) + self.assertEqual(box.zMaximum(), 10.0) + + box = QgsBox3d(QgsVector3D(3, 4, 5), QgsVector3D(1, 2, 6)) + self.assertEqual(box.xMinimum(), 1.0) + self.assertEqual(box.yMinimum(), 2.0) + self.assertEqual(box.zMinimum(), 5.0) + self.assertEqual(box.xMaximum(), 3.0) + self.assertEqual(box.yMaximum(), 4.0) + self.assertEqual(box.zMaximum(), 6.0) + + box = QgsBox3d(QgsVector3D(3, 4, 5), QgsVector3D(1, 2, 6), False) + self.assertEqual(box.xMinimum(), 3.0) + self.assertEqual(box.yMinimum(), 4.0) + self.assertEqual(box.zMinimum(), 5.0) + self.assertEqual(box.xMaximum(), 1.0) + self.assertEqual(box.yMaximum(), 2.0) + self.assertEqual(box.zMaximum(), 6.0) + def test_repr(self): box = QgsBox3d(5.0, 6.0, 7.0, 10.0, 11.0, 12.0) self.assertEqual(str(box), '') @@ -391,6 +416,16 @@ class TestQgsBox3d(unittest.TestCase): self.assertEqual(box3.yMinimum(), -7.0) self.assertEqual(box3.zMinimum(), -3.0) + def testGrow(self): + box = QgsBox3d(5.0, 6.0, 7.0, 11.0, 13.0, 15.0) + box.grow(2.0) + self.assertEqual(box.xMinimum(), 3.0) + self.assertEqual(box.yMinimum(), 4.0) + self.assertEqual(box.zMinimum(), 5.0) + self.assertEqual(box.xMaximum(), 13.0) + self.assertEqual(box.yMaximum(), 15.0) + self.assertEqual(box.zMaximum(), 17.0) + def testIsNull(self): box1 = QgsBox3d() self.assertTrue(box1.isNull()) diff --git a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py index 64fe7208a5a..13806cb9779 100644 --- a/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py +++ b/tests/src/python/test_qgsgeometrygeneratorsymbollayer.py @@ -35,17 +35,20 @@ from qgis.core import ( QgsGeometry, QgsGeometryGeneratorSymbolLayer, QgsLineSymbol, + QgsMapRendererSequentialJob, QgsMapSettings, QgsMarkerSymbol, QgsProject, QgsProperty, QgsRectangle, + QgsReferencedGeometry, QgsRenderContext, QgsSingleSymbolRenderer, QgsSymbol, QgsSymbolLayer, QgsUnitTypes, QgsVectorLayer, + QgsVectorLayerUtils, ) import unittest from qgis.testing import start_app, QgisTestCase @@ -463,6 +466,31 @@ class TestQgsGeometryGeneratorSymbolLayerV2(QgisTestCase): ) ) + def test_field_geometry(self): + """ + Use a geometry field + """ + + points = QgsVectorLayer('Point?crs=epsg:2154&field=other_geom:geometry(0,0)', 'Points', 'memory') + f = QgsVectorLayerUtils.createFeature(points, + QgsGeometry.fromWkt('Point(5 4)'), + {0: QgsReferencedGeometry(QgsGeometry.fromWkt('LineString(5 6, 7 8)'), QgsCoordinateReferenceSystem("EPSG:4326"))}) + points.dataProvider().addFeature(f) + other_layer = QgsGeometryGeneratorSymbolLayer.create({'geometryModifier': '"other_geom"', 'outline_color': 'black', 'SymbolType': 'Line', 'line_width': 2}) + points.renderer().symbol().changeSymbolLayer(0, other_layer) + + mapsettings = QgsMapSettings(self.mapsettings) + mapsettings.setExtent(QgsRectangle(0, 0, 10, 10)) + mapsettings.setLayers([points]) + + self.assertTrue( + self.render_map_settings_check( + 'geometrygenerator_field_geometry', + 'geometrygenerator_field_geometry', + mapsettings + ) + ) + def test_feature_geometry(self): """ The geometry($currentfeature) expression used in a subsymbol should refer to the original FEATURE geometry diff --git a/tests/src/python/test_qgssettingseditorregistry.py b/tests/src/python/test_qgssettingseditorregistry.py new file mode 100644 index 00000000000..390b878b7b8 --- /dev/null +++ b/tests/src/python/test_qgssettingseditorregistry.py @@ -0,0 +1,75 @@ +""" +Test the PyQgsSettingsRegistry classes + +Run with: ctest -V -R PyQgsSettingsRegistry + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" + +from qgis.PyQt.QtWidgets import QComboBox, QSpinBox +from qgis.core import ( + QgsLocatorFilter, + QgsSettings, + QgsSettingsTree, + QgsSettingsEntryEnumFlag, + QgsSettingsEntryInteger, +) +from qgis.gui import QgsGui, QgsSettingsEditorWidgetWrapper, QgsSettingsEnumEditorWidgetWrapper +import unittest +from qgis.testing import start_app, QgisTestCase + + +start_app() + +PLUGIN_NAME = "UnitTestSettingsRegistry" + + +class PyQgsSettingsRegistry(QgisTestCase): + + def setUp(self): + self.settings_node = QgsSettingsTree.createPluginTreeNode(pluginName=PLUGIN_NAME) + + def tearDown(self): + QgsSettingsTree.unregisterPluginTreeNode(PLUGIN_NAME) + + def test_settings_registry(self): + int_setting = QgsSettingsEntryInteger("int_setting", self.settings_node, 77) + registry = QgsGui.settingsEditorWidgetRegistry() + + editor = registry.createEditor(int_setting, []) + self.assertIsInstance(editor, QSpinBox) + + wrapper = QgsSettingsEditorWidgetWrapper.fromWidget(editor) + self.assertEqual(editor.value(), 77) + + editor.setValue(6) + self.assertEqual(wrapper.variantValueFromWidget(), 6) + wrapper.setSettingFromWidget() + self.assertEqual(int_setting.value(), 6) + + def test_settings_registry_custom_enumflag_py(self): + priority_setting = QgsSettingsEntryEnumFlag("priority", self.settings_node, QgsLocatorFilter.Priority.High) + registry = QgsGui.settingsEditorWidgetRegistry() + registry.addWrapperForSetting(QgsSettingsEnumEditorWidgetWrapper(), priority_setting) + + editor = registry.createEditor(priority_setting, []) + self.assertIsInstance(editor, QComboBox) + wrapper = QgsSettingsEditorWidgetWrapper.fromWidget(editor) + + self.assertEqual(editor.currentData(), "High") + + editor.setCurrentIndex(editor.findData("Low")) + + self.assertEqual(wrapper.variantValueFromWidget(), "Low") + + wrapper.setSettingFromWidget() + + self.assertEqual(priority_setting.value(), QgsLocatorFilter.Priority.Low) + self.assertEqual(QgsSettings().value(f"plugins/{PLUGIN_NAME}/priority"), "Low") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgstextformat.py b/tests/src/python/test_qgstextformat.py new file mode 100644 index 00000000000..b4f4fe6ab35 --- /dev/null +++ b/tests/src/python/test_qgstextformat.py @@ -0,0 +1,55 @@ +"""QGIS Unit tests for QgsTextFormat. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Mathieu Pellerin' +__date__ = '2024-10-20' +__copyright__ = 'Copyright 2024, The QGIS Project' + +from qgis.PyQt.QtGui import QFont +from qgis.PyQt.QtXml import ( + QDomDocument, + QDomElement, +) +from qgis.core import ( + QgsTextFormat, + QgsReadWriteContext, +) +import unittest +from qgis.testing import start_app, QgisTestCase +from utilities import getTestFont + +start_app() + + +class PyQgsTextFormat(QgisTestCase): + + def testRestoringAndSavingMissingFont(self): + # test that a missing font on text format load will still save with the same missing font unless manually changed + document = QDomDocument() + document.setContent('') + + context = QgsReadWriteContext() + text_format = QgsTextFormat() + text_format.readXml(document.documentElement(), context) + + self.assertFalse(text_format.fontFound()) + self.assertTrue(text_format.font().family() != "__MISSING__") + + # when writign the settings to XML, the missing font family should still be there + element = text_format.writeXml(document, context) + self.assertEqual(element.attribute("fontFamily"), "__MISSING__") + + font = getTestFont() + text_format.setFont(font) + + # when writing the settings to XML, the originally missing font family should have been replaced by the new font family + element = text_format.writeXml(document, context) + self.assertEqual(element.attribute("fontFamily"), "QGIS Vera Sans") + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 7146cc3e679..79d0644e490 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -1674,7 +1674,7 @@ class PyQgsTextRenderer(QgisTestCase): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(1100) - assert self.checkRender(format, 'massive_font', rect=QRectF(-800, -600, 1000, 1000), text=['a t'], image_size=800) + self.assertTrue(self.checkRender(format, 'massive_font', rect=QRectF(-800, -600, 1000, 1000), text=['a t'], image_size=800)) def testDrawRectMixedHtml(self): """ @@ -1684,7 +1684,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setAllowHtmlFormatting(True) format.setSize(30) - assert self.checkRender(format, 'rect_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line']) + self.assertTrue(self.checkRender(format, 'rect_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'])) def testDrawDocumentRect(self): """ @@ -1736,7 +1736,7 @@ class PyQgsTextRenderer(QgisTestCase): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) - assert self.checkRender(format, 'rect_cap_height_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased) + self.assertTrue(self.checkRender(format, 'rect_cap_height_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased)) def testDrawRectCapHeightModeMixedHtml(self): """ @@ -1746,7 +1746,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setAllowHtmlFormatting(True) format.setSize(30) - assert self.checkRender(format, 'rect_cap_height_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased) + self.assertTrue(self.checkRender(format, 'rect_cap_height_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleCapHeightBased)) def testDrawDocumentRectCapHeightMode(self): """ @@ -1798,7 +1798,7 @@ class PyQgsTextRenderer(QgisTestCase): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) - assert self.checkRender(format, 'rect_ascent_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased) + self.assertTrue(self.checkRender(format, 'rect_ascent_mode', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased)) def testDrawRectAscentModeMixedHtml(self): """ @@ -1808,7 +1808,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setAllowHtmlFormatting(True) format.setSize(30) - assert self.checkRender(format, 'rect_ascent_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased) + self.assertTrue(self.checkRender(format, 'rect_ascent_mode_html', rect=QRectF(100, 100, 100, 100), text=['first line', 'second line', 'third line'], mode=Qgis.TextLayoutMode.RectangleAscentBased)) def testDrawDocumentRectAscentMode(self): """ @@ -1912,7 +1912,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont()) format.setSize(30) format.setForcedItalic(True) - assert self.checkRender(format, 'forced_italic', text=['Forced italic']) + self.assertTrue(self.checkRender(format, 'forced_italic', text=['Forced italic'])) def testDrawRTL(self): """ @@ -2094,7 +2094,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setCapitalization(Qgis.Capitalization.SmallCaps) format.setSize(30) - assert self.checkRender(format, 'mixed_small_caps', text=['Small Caps']) + self.assertTrue(self.checkRender(format, 'mixed_small_caps', text=['Small Caps'])) @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawAllSmallCaps(self): @@ -2102,7 +2102,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setSize(30) format.setCapitalization(Qgis.Capitalization.AllSmallCaps) - assert self.checkRender(format, 'all_small_caps', text=['Small Caps']) + self.assertTrue(self.checkRender(format, 'all_small_caps', text=['Small Caps'])) @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawStretch(self): @@ -2110,7 +2110,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setSize(30) format.setStretchFactor(150) - assert self.checkRender(format, 'stretch_expand') + self.assertTrue(self.checkRender(format, 'stretch_expand')) @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawStretchCondense(self): @@ -2118,13 +2118,13 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setSize(30) format.setStretchFactor(50) - assert self.checkRender(format, 'stretch_condense') + self.assertTrue(self.checkRender(format, 'stretch_condense')) def testDrawBackgroundDisabled(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.background().setEnabled(False) - assert self.checkRender(format, 'background_disabled', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_disabled', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectangleFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2134,7 +2134,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_rect_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectangleFixedSizeWithRotatedText(self): format = QgsTextFormat() @@ -2145,7 +2145,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRenderPoint(format, 'background_rect_fixed_rotated_text', angle=3.141 / 4) + self.assertTrue(self.checkRenderPoint(format, 'background_rect_fixed_rotated_text', angle=3.141 / 4)) def testDrawBackgroundRectangleBufferSizeWithRotatedText(self): format = QgsTextFormat() @@ -2156,7 +2156,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(2, 3)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRenderPoint(format, 'background_rect_buffer_rotated_text', angle=3.141 / 4) + self.assertTrue(self.checkRenderPoint(format, 'background_rect_buffer_rotated_text', angle=3.141 / 4)) def testDrawBackgroundRectangleMultilineFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2166,8 +2166,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_multiline_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'background_rect_multiline_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundPointMultilineFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2177,8 +2177,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_multiline_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRenderPoint(format, 'background_point_multiline_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundRectangleMultilineBufferMapUnits(self): format = QgsTextFormat() @@ -2188,8 +2188,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(4, 2)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'background_rect_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundPointMultilineBufferMapUnits(self): format = QgsTextFormat() @@ -2199,8 +2199,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(4, 2)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRenderPoint(format, 'background_point_multiline_buffer_mapunits', QgsTextRenderer.TextPart.Background, + text=['test', 'multi', 'line'])) def testDrawBackgroundPointFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2210,8 +2210,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_mapunits', QgsTextRenderer.TextPart.Background, - text=['Testy']) + self.assertTrue(self.checkRenderPoint(format, 'background_point_mapunits', QgsTextRenderer.TextPart.Background, + text=['Testy'])) def testDrawBackgroundRectangleCenterAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2221,8 +2221,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_center_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter) + self.assertTrue(self.checkRender(format, 'background_rect_center_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter)) def testDrawBackgroundPointCenterAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2232,8 +2232,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_center_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter) + self.assertTrue(self.checkRenderPoint(format, 'background_point_center_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter)) def testDrawBackgroundRectangleRightAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2243,8 +2243,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_right_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRender(format, 'background_rect_right_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight)) def testDrawBackgroundPointRightAlignFixedSizeMapUnits(self): format = QgsTextFormat() @@ -2254,8 +2254,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRenderPoint(format, 'background_point_right_mapunits', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRenderPoint(format, 'background_point_right_mapunits', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight)) def testDrawBackgroundRectangleFixedSizeMM(self): format = QgsTextFormat() @@ -2265,7 +2265,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_rect_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_rect_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectangleFixedSizePixels(self): format = QgsTextFormat() @@ -2275,7 +2275,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_rect_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRectBufferPixels(self): format = QgsTextFormat() @@ -2288,8 +2288,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_buffer_pixels', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_buffer_pixels', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRectRightAlignBufferPixels(self): format = QgsTextFormat() @@ -2302,9 +2302,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_right_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_right_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRectCenterAlignBufferPixels(self): format = QgsTextFormat() @@ -2317,9 +2317,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_rect_center_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_center_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundPointBufferPixels(self): format = QgsTextFormat() @@ -2332,8 +2332,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRenderPoint(format, 'background_point_buffer_pixels', QgsTextRenderer.TextPart.Background, - point=QPointF(100, 100)) + self.assertTrue(self.checkRenderPoint(format, 'background_point_buffer_pixels', QgsTextRenderer.TextPart.Background, + point=QPointF(100, 100))) def testDrawBackgroundPointRightAlignBufferPixels(self): format = QgsTextFormat() @@ -2346,9 +2346,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRenderPoint(format, 'background_point_right_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignRight, - point=QPointF(100, 100)) + self.assertTrue(self.checkRenderPoint(format, 'background_point_right_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignRight, + point=QPointF(100, 100))) def testDrawBackgroundPointCenterAlignBufferPixels(self): format = QgsTextFormat() @@ -2361,9 +2361,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 50)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRenderPoint(format, 'background_point_center_buffer_pixels', QgsTextRenderer.TextPart.Background, - alignment=QgsTextRenderer.HAlignment.AlignCenter, - point=QPointF(100, 100)) + self.assertTrue(self.checkRenderPoint(format, 'background_point_center_buffer_pixels', QgsTextRenderer.TextPart.Background, + alignment=QgsTextRenderer.HAlignment.AlignCenter, + point=QPointF(100, 100))) def testDrawBackgroundRectBufferMapUnits(self): format = QgsTextFormat() @@ -2376,8 +2376,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(4, 6)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_rect_buffer_mapunits', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_buffer_mapunits', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRectBufferMM(self): format = QgsTextFormat() @@ -2390,8 +2390,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(10, 16)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_rect_buffer_mm', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_rect_buffer_mm', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundEllipse(self): format = QgsTextFormat() @@ -2401,7 +2401,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_ellipse_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_ellipse_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundSvgFixedPixels(self): format = QgsTextFormat() @@ -2414,7 +2414,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_svg_fixed_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_svg_fixed_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundSvgFixedMapUnits(self): format = QgsTextFormat() @@ -2427,7 +2427,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_svg_fixed_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_svg_fixed_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundSvgFixedMM(self): format = QgsTextFormat() @@ -2440,7 +2440,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_svg_fixed_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_svg_fixed_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRotationSynced(self): format = QgsTextFormat() @@ -2452,7 +2452,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRotation(45) # should be ignored format.background().setRotationType(QgsTextBackgroundSettings.RotationType.RotationSync) - assert self.checkRender(format, 'background_rotation_sync', QgsTextRenderer.TextPart.Background, angle=20) + self.assertTrue(self.checkRender(format, 'background_rotation_sync', QgsTextRenderer.TextPart.Background, angle=20)) def testDrawBackgroundSvgBufferPixels(self): format = QgsTextFormat() @@ -2465,8 +2465,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_svg_buffer_pixels', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_svg_buffer_pixels', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundSvgBufferMapUnits(self): format = QgsTextFormat() @@ -2479,8 +2479,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(4, 4)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_svg_buffer_mapunits', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_svg_buffer_mapunits', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundSvgBufferMM(self): format = QgsTextFormat() @@ -2493,8 +2493,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(10, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_svg_buffer_mm', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_svg_buffer_mm', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundMarkerFixedPixels(self): format = QgsTextFormat() @@ -2506,7 +2506,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(60, 80)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_marker_fixed_pixels', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_pixels', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundMarkerFixedReferenceScale(self): format = QgsTextFormat() @@ -2519,8 +2519,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(6, 8)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_marker_fixed_reference_scale', - reference_scale=10000, renderer_scale=5000) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_reference_scale', + reference_scale=10000, renderer_scale=5000)) def testDrawBackgroundMarkerFixedMapUnits(self): format = QgsTextFormat() @@ -2532,7 +2532,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_marker_fixed_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundMarkerFixedMM(self): format = QgsTextFormat() @@ -2544,7 +2544,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_marker_fixed_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_marker_fixed_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundMarkerBufferPixels(self): format = QgsTextFormat() @@ -2556,8 +2556,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 30)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'background_marker_buffer_pixels', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_marker_buffer_pixels', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundMarkerBufferMapUnits(self): format = QgsTextFormat() @@ -2569,8 +2569,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(4, 4)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_marker_buffer_mapunits', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_marker_buffer_mapunits', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundMarkerBufferMM(self): format = QgsTextFormat() @@ -2582,8 +2582,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(10, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeBuffer) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_marker_buffer_mm', QgsTextRenderer.TextPart.Background, - rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'background_marker_buffer_mm', QgsTextRenderer.TextPart.Background, + rect=QRectF(100, 100, 100, 100))) def testDrawBackgroundRotationFixed(self): format = QgsTextFormat() @@ -2595,7 +2595,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRotation(45) format.background().setRotationType(QgsTextBackgroundSettings.RotationType.RotationFixed) - assert self.checkRender(format, 'background_rotation_fixed', QgsTextRenderer.TextPart.Background, angle=20) + self.assertTrue(self.checkRender(format, 'background_rotation_fixed', QgsTextRenderer.TextPart.Background, angle=20)) def testDrawRotationOffset(self): format = QgsTextFormat() @@ -2607,7 +2607,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRotation(45) format.background().setRotationType(QgsTextBackgroundSettings.RotationType.RotationOffset) - assert self.checkRender(format, 'background_rotation_offset', QgsTextRenderer.TextPart.Background, angle=20) + self.assertTrue(self.checkRender(format, 'background_rotation_offset', QgsTextRenderer.TextPart.Background, angle=20)) def testDrawBackgroundOffsetMM(self): format = QgsTextFormat() @@ -2619,7 +2619,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setOffset(QPointF(30, 20)) format.background().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_offset_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_offset_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundOffsetMapUnits(self): format = QgsTextFormat() @@ -2631,7 +2631,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setOffset(QPointF(10, 5)) format.background().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_offset_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_offset_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRadiiMM(self): format = QgsTextFormat() @@ -2643,7 +2643,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRadii(QSizeF(6, 4)) format.background().setRadiiUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_radii_mm', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_radii_mm', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundRadiiMapUnits(self): format = QgsTextFormat() @@ -2655,7 +2655,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setRadii(QSizeF(3, 2)) format.background().setRadiiUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'background_radii_mapunits', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_radii_mapunits', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundOpacity(self): format = QgsTextFormat() @@ -2665,7 +2665,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setOpacity(0.6) - assert self.checkRender(format, 'background_opacity', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_opacity', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundFillColor(self): format = QgsTextFormat() @@ -2675,7 +2675,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setFillColor(QColor(50, 100, 50)) - assert self.checkRender(format, 'background_fillcolor', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_fillcolor', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundFillSymbol(self): format = QgsTextFormat() @@ -2693,7 +2693,7 @@ class PyQgsTextRenderer(QgisTestCase): fill.setStrokeWidth(6) format.background().setFillSymbol(fill_symbol) - assert self.checkRender(format, 'background_fillsymbol', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_fillsymbol', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundStroke(self): format = QgsTextFormat() @@ -2705,7 +2705,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setStrokeColor(QColor(50, 100, 50)) format.background().setStrokeWidth(3) format.background().setStrokeWidthUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'background_outline', QgsTextRenderer.TextPart.Background) + self.assertTrue(self.checkRender(format, 'background_outline', QgsTextRenderer.TextPart.Background)) def testDrawBackgroundEffect(self): format = QgsTextFormat() @@ -2717,21 +2717,21 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(30, 20)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setPaintEffect(QgsBlurEffect.create({'blur_level': '10', 'enabled': '1'})) - assert self.checkRender(format, 'background_effect', QgsTextRenderer.TextPart.Background, text=['test']) + self.assertTrue(self.checkRender(format, 'background_effect', QgsTextRenderer.TextPart.Background, text=['test'])) def testDrawText(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_bold', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_bold', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextPoint(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_bold', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRenderPoint(format, 'text_point_bold', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextNamedStyle(self): format = QgsTextFormat() @@ -2742,7 +2742,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setNamedStyle('Bold Oblique') format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_named_style', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_named_style', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextColor(self): format = QgsTextFormat() @@ -2750,7 +2750,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) - assert self.checkRender(format, 'text_color', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_color', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextOpacity(self): format = QgsTextFormat() @@ -2758,7 +2758,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOpacity(0.7) - assert self.checkRender(format, 'text_opacity', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_opacity', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextBlendMode(self): format = QgsTextFormat() @@ -2767,43 +2767,43 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(100, 100, 100)) format.setBlendMode(QPainter.CompositionMode.CompositionMode_Difference) - assert self.checkRender(format, 'text_blend_mode', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_blend_mode', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextAngle(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_angled', QgsTextRenderer.TextPart.Text, angle=90 / 180 * 3.141, text=['test']) + self.assertTrue(self.checkRender(format, 'text_angled', QgsTextRenderer.TextPart.Text, angle=90 / 180 * 3.141, text=['test'])) def testDrawTextMapUnits(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(5) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_mapunits', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawTextPixels(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(50) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'text_pixels', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'text_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawMultiLineText(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_multiline', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_multiline', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawMultiLineTextPoint(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_multiline', QgsTextRenderer.TextPart.Text, - text=['test', 'multi', 'line']) + self.assertTrue(self.checkRenderPoint(format, 'text_point_multiline', QgsTextRenderer.TextPart.Text, + text=['test', 'multi', 'line'])) def testDrawLineHeightText(self): format = QgsTextFormat() @@ -2811,7 +2811,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setLineHeight(1.5) - assert self.checkRender(format, 'text_line_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_line_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawLineHeightAbsolutePoints(self): format = QgsTextFormat() @@ -2820,7 +2820,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setLineHeight(20) format.setLineHeightUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_line_absolute_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_line_absolute_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawLineHeightAbsoluteMillimeters(self): format = QgsTextFormat() @@ -2829,7 +2829,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setLineHeight(20) format.setLineHeightUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_line_absolute_mm_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line']) + self.assertTrue(self.checkRender(format, 'text_line_absolute_mm_height', QgsTextRenderer.TextPart.Text, text=['test', 'multi', 'line'])) def testDrawBufferSizeMM(self): format = QgsTextFormat() @@ -2839,7 +2839,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_buffer_mm', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_mm', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferDisabled(self): format = QgsTextFormat() @@ -2847,7 +2847,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.buffer().setEnabled(False) - assert self.checkRender(format, 'text_disabled_buffer', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_disabled_buffer', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferSizeMapUnits(self): format = QgsTextFormat() @@ -2857,7 +2857,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_buffer_mapunits', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_mapunits', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferSizePixels(self): format = QgsTextFormat() @@ -2867,7 +2867,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(10) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'text_buffer_pixels', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_pixels', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferSizePercentage(self): format = QgsTextFormat() @@ -2877,7 +2877,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(10) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - assert self.checkRender(format, 'text_buffer_percentage', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_percentage', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferColor(self): format = QgsTextFormat() @@ -2888,7 +2888,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setColor(QColor(0, 255, 0)) - assert self.checkRender(format, 'text_buffer_color', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_color', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferOpacity(self): format = QgsTextFormat() @@ -2899,7 +2899,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setOpacity(0.5) - assert self.checkRender(format, 'text_buffer_opacity', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_opacity', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferFillInterior(self): format = QgsTextFormat() @@ -2910,7 +2910,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setFillBufferInterior(True) - assert self.checkRender(format, 'text_buffer_interior', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_interior', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawBufferEffect(self): format = QgsTextFormat() @@ -2921,7 +2921,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(2) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.buffer().setPaintEffect(QgsBlurEffect.create({'blur_level': '10', 'enabled': '1'})) - assert self.checkRender(format, 'text_buffer_effect', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'text_buffer_effect', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawShadow(self): format = QgsTextFormat() @@ -2935,7 +2935,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_enabled', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_enabled', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetAngle(self): format = QgsTextFormat() @@ -2950,7 +2950,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setOffsetAngle(0) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_offset_angle', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_angle', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetMapUnits(self): format = QgsTextFormat() @@ -2964,7 +2964,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'shadow_offset_mapunits', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetPixels(self): format = QgsTextFormat() @@ -2978,7 +2978,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'shadow_offset_pixels', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOffsetPercentage(self): format = QgsTextFormat() @@ -2992,7 +2992,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(10) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - assert self.checkRender(format, 'shadow_offset_percentage', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_offset_percentage', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusMM(self): format = QgsTextFormat() @@ -3007,7 +3007,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(1) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_radius_mm', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_mm', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusMapUnits(self): format = QgsTextFormat() @@ -3022,7 +3022,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(3) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'shadow_radius_mapunits', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_mapunits', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusPixels(self): format = QgsTextFormat() @@ -3037,7 +3037,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(3) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderPixels) - assert self.checkRender(format, 'shadow_radius_pixels', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_pixels', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBlurRadiusPercentage(self): format = QgsTextFormat() @@ -3052,7 +3052,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.shadow().setBlurRadius(5) format.shadow().setBlurRadiusUnit(QgsUnitTypes.RenderUnit.RenderPercentage) - assert self.checkRender(format, 'shadow_radius_percentage', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_radius_percentage', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowOpacity(self): format = QgsTextFormat() @@ -3066,7 +3066,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_opacity', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_opacity', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowColor(self): format = QgsTextFormat() @@ -3080,7 +3080,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_color', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_color', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowWithJustifyAlign(self): format = QgsTextFormat() @@ -3093,9 +3093,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_justify_aligned_with_shadow', - text=['a t est', 'off', 'justification', 'align'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_justify_aligned_with_shadow', + text=['a t est', 'off', 'justification', 'align'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100))) def testDrawShadowScale(self): format = QgsTextFormat() @@ -3109,7 +3109,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_scale_50', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_scale_50', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowScaleUp(self): format = QgsTextFormat() @@ -3123,7 +3123,7 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setOffsetDistance(5) format.shadow().setOffsetUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_scale_150', QgsTextRenderer.TextPart.Text, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_scale_150', QgsTextRenderer.TextPart.Text, text=['test'])) def testDrawShadowBackgroundPlacement(self): format = QgsTextFormat() @@ -3141,7 +3141,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'shadow_placement_background', QgsTextRenderer.TextPart.Background, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_placement_background', QgsTextRenderer.TextPart.Background, text=['test'])) def testDrawShadowBufferPlacement(self): format = QgsTextFormat() @@ -3157,7 +3157,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'shadow_placement_buffer', QgsTextRenderer.TextPart.Buffer, text=['test']) + self.assertTrue(self.checkRender(format, 'shadow_placement_buffer', QgsTextRenderer.TextPart.Buffer, text=['test'])) def testDrawTextWithBuffer(self): format = QgsTextFormat() @@ -3167,7 +3167,26 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_buffer', text=['test'], rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_buffer', text=['test'], rect=QRectF(100, 100, 200, 100))) + + def testDrawTextWithBufferBlendMode(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(60) + format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) + format.background().setEnabled(True) + format.background().setType(QgsTextBackgroundSettings.ShapeType.ShapeRectangle) + format.background().setSize(QSizeF(20, 10)) + format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) + format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) + format.background().setFillColor(QColor(200, 100, 150)) + format.buffer().setEnabled(True) + format.buffer().setSize(4) + format.buffer().setColor(QColor(100, 255, 100)) + format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) + format.buffer().setBlendMode(QPainter.CompositionMode.CompositionMode_Multiply) + self.assertTrue(self.checkRender(format, 'text_with_buffer_blend_mode', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackground(self): format = QgsTextFormat() @@ -3179,7 +3198,7 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_with_background', text=['test'], rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_background', text=['test'], rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBufferAndBackground(self): format = QgsTextFormat() @@ -3195,8 +3214,8 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithShadowAndBuffer(self): format = QgsTextFormat() @@ -3213,7 +3232,7 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_and_buffer', text=['test'], rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_and_buffer', text=['test'], rect=QRectF(100, 100, 200, 100))) def testDrawTextWithShadowBelowTextAndBuffer(self): format = QgsTextFormat() @@ -3231,8 +3250,8 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_below_text_and_buffer', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_text_and_buffer', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundAndShadow(self): format = QgsTextFormat() @@ -3250,8 +3269,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_with_shadow_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithShadowBelowTextAndBackground(self): format = QgsTextFormat() @@ -3270,8 +3289,8 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSize(QSizeF(20, 10)) format.background().setSizeType(QgsTextBackgroundSettings.SizeType.SizeFixed) format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMapUnits) - assert self.checkRender(format, 'text_with_shadow_below_text_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_text_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundBufferAndShadow(self): format = QgsTextFormat() @@ -3293,8 +3312,8 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundBufferAndShadowBelowText(self): format = QgsTextFormat() @@ -3317,8 +3336,8 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_below_text_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_text_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextWithBackgroundBufferAndShadowBelowBuffer(self): format = QgsTextFormat() @@ -3341,24 +3360,24 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(4) format.buffer().setColor(QColor(100, 255, 100)) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_with_shadow_below_buffer_and_background', text=['test'], - rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_with_shadow_below_buffer_and_background', text=['test'], + rect=QRectF(100, 100, 200, 100))) def testDrawTextRectMultilineRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_right_aligned', text=['test', 'right', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_right_aligned', text=['test', 'right', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_right_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_right_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignRight, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectMultilineJustifyAlign(self): format = QgsTextFormat() @@ -3368,17 +3387,17 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_rect_multiline_justify_aligned', - text=['a t est', 'off', 'justification', 'align'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_justify_aligned', + text=['a t est', 'off', 'justification', 'align'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectJustifyAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_justify_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_justify_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectMultiparagraphJustifyAlign(self): format = QgsTextFormat() @@ -3388,9 +3407,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(4) format.buffer().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) - assert self.checkRender(format, 'text_rect_multiparagraph_justify_aligned', - text=['a t est', 'of justify', '', 'with two', 'pgraphs'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(50, 100, 250, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiparagraph_justify_aligned', + text=['a t est', 'of justify', '', 'with two', 'pgraphs'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(50, 100, 250, 100))) def testDrawTextRectWordWrapSingleLine(self): format = QgsTextFormat() @@ -3422,9 +3441,9 @@ class PyQgsTextRenderer(QgisTestCase): mode=QgsTextRenderer.DrawMode.Rect, flags=Qgis.TextRendererFlag.WrapLines, maxLineWidth=200), QgsTextRenderer.textHeight(context, format, ['a test of word wrap'], mode=QgsTextRenderer.DrawMode.Rect) * 2.75) - assert self.checkRender(format, 'text_rect_word_wrap_single_line', text=['a test of word wrap'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - flags=Qgis.TextRendererFlag.WrapLines) + self.assertTrue(self.checkRender(format, 'text_rect_word_wrap_single_line', text=['a test of word wrap'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + flags=Qgis.TextRendererFlag.WrapLines)) def testWordWrapSingleLineStabilityAtSmallScaling(self): format = QgsTextFormat() @@ -3494,18 +3513,18 @@ class PyQgsTextRenderer(QgisTestCase): mode=QgsTextRenderer.DrawMode.Rect, flags=Qgis.TextRendererFlag.WrapLines, maxLineWidth=200), QgsTextRenderer.textHeight(context, format, ['a test of word wrap with with bit more'], mode=QgsTextRenderer.DrawMode.Rect) * 4.75) - assert self.checkRender(format, 'text_rect_word_wrap_multi_line', text=['a test of word wrap', 'with bit more'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - flags=Qgis.TextRendererFlag.WrapLines) + self.assertTrue(self.checkRender(format, 'text_rect_word_wrap_multi_line', text=['a test of word wrap', 'with bit more'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + flags=Qgis.TextRendererFlag.WrapLines)) def testDrawTextRectWordWrapWithJustify(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_word_wrap_justify', text=['a test of word wrap'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100), - flags=Qgis.TextRendererFlag.WrapLines) + self.assertTrue(self.checkRender(format, 'text_rect_word_wrap_justify', text=['a test of word wrap'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, rect=QRectF(100, 100, 200, 100), + flags=Qgis.TextRendererFlag.WrapLines)) def testDrawTextRectWordWrapHtml1(self): format = QgsTextFormat() @@ -3595,9 +3614,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_bottom_aligned', text=['test', 'bottom', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignBottom) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_bottom_aligned', text=['test', 'bottom', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignBottom)) def testDrawTextRectBottomAlign(self): format = QgsTextFormat() @@ -3605,9 +3624,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_bottom_aligned', text=['bottom aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignBottom) + self.assertTrue(self.checkRender(format, 'text_rect_bottom_aligned', text=['bottom aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignBottom)) def testDrawTextRectMultilineVCenterAlign(self): format = QgsTextFormat() @@ -3615,9 +3634,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_vcenter_aligned', text=['test', 'center', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignVCenter) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_vcenter_aligned', text=['test', 'center', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignVCenter)) def testDrawTextRectVCenterAlign(self): format = QgsTextFormat() @@ -3625,17 +3644,17 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_vcenter_aligned', text=['center aligned'], - alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), - vAlignment=QgsTextRenderer.VAlignment.AlignVCenter) + self.assertTrue(self.checkRender(format, 'text_rect_vcenter_aligned', text=['center aligned'], + alignment=QgsTextRenderer.HAlignment.AlignLeft, rect=QRectF(100, 100, 200, 100), + vAlignment=QgsTextRenderer.VAlignment.AlignVCenter)) def testDrawTextRectMultilineCenterAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_multiline_center_aligned', text=['test', 'c', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_multiline_center_aligned', text=['test', 'c', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100))) def testDrawTextRectCenterAlign(self): format = QgsTextFormat() @@ -3643,57 +3662,57 @@ class PyQgsTextRenderer(QgisTestCase): format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRender(format, 'text_rect_center_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100)) + self.assertTrue(self.checkRender(format, 'text_rect_center_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 200, 100))) def testDrawTextPointMultilineRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_right_multiline_aligned', text=['test', 'right', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_right_multiline_aligned', text=['test', 'right', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200))) def testDrawTextPointMultilineCenterAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_center_multiline_aligned', text=['test', 'center', 'aligned'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_center_multiline_aligned', text=['test', 'center', 'aligned'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200))) def testDrawTextPointRightAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_right_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_right_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignRight, point=QPointF(300, 200))) def testDrawTextPointJustifyAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_justify_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_justify_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200))) def testDrawTextPointMultilineJustifyAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_justify_multiline_aligned', - text=['a t est', 'off', 'justification', 'align'], - alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_justify_multiline_aligned', + text=['a t est', 'off', 'justification', 'align'], + alignment=QgsTextRenderer.HAlignment.AlignJustify, point=QPointF(100, 200))) def testDrawTextPointCenterAlign(self): format = QgsTextFormat() format.setFont(getTestFont('bold')) format.setSize(30) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) - assert self.checkRenderPoint(format, 'text_point_center_aligned', text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_point_center_aligned', text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, point=QPointF(200, 200))) def testDrawTextDataDefinedColorPoint(self): format = QgsTextFormat() @@ -3702,7 +3721,7 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.Color, QgsProperty.fromExpression("'#bb00cc'")) - assert self.checkRenderPoint(format, 'text_dd_color_point', None, text=['test'], point=QPointF(50, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_dd_color_point', None, text=['test'], point=QPointF(50, 200))) def testDrawTextDataDefinedColorRect(self): format = QgsTextFormat() @@ -3711,8 +3730,8 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.dataDefinedProperties().setProperty(QgsPalLayerSettings.Property.Color, QgsProperty.fromExpression("'#bb00cc'")) - assert self.checkRender(format, 'text_dd_color_rect', None, text=['test'], - alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 100, 100)) + self.assertTrue(self.checkRender(format, 'text_dd_color_rect', None, text=['test'], + alignment=QgsTextRenderer.HAlignment.AlignCenter, rect=QRectF(100, 100, 100, 100))) def testDrawTextDataDefinedBufferColorPoint(self): format = QgsTextFormat() @@ -3724,7 +3743,7 @@ class PyQgsTextRenderer(QgisTestCase): QgsProperty.fromExpression("'#bb00cc'")) format.buffer().setEnabled(True) format.buffer().setSize(5) - assert self.checkRenderPoint(format, 'text_dd_buffer_color', None, text=['test'], point=QPointF(50, 200)) + self.assertTrue(self.checkRenderPoint(format, 'text_dd_buffer_color', None, text=['test'], point=QPointF(50, 200))) def testDrawTabPercent(self): format = QgsTextFormat() @@ -3755,9 +3774,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_formatting', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlTabPercent(self): format = QgsTextFormat() @@ -3796,9 +3815,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingBufferScaleFactor(self): """ @@ -3814,9 +3833,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer_scale_workaround', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer_scale_workaround', None, text=[ 't e s'], - point=QPointF(50, 200), enable_scale_workaround=True) + point=QPointF(50, 200), enable_scale_workaround=True)) def testHtmlFormattingMask(self): """ @@ -3830,9 +3849,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setAllowHtmlFormatting(True) format.mask().setEnabled(True) format.mask().setSize(5) - assert self.checkRenderPoint(format, 'text_html_formatting_mask', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_mask', None, text=[ 't e s'], - point=QPointF(50, 200), render_mask=True) + point=QPointF(50, 200), render_mask=True)) def testHtmlFormattingMaskScaleFactor(self): """ @@ -3847,9 +3866,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setAllowHtmlFormatting(True) format.mask().setEnabled(True) format.mask().setSize(5) - assert self.checkRenderPoint(format, 'text_html_formatting_mask_scale_workaround', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_mask_scale_workaround', None, text=[ 't e s'], - point=QPointF(50, 200), render_mask=True, enable_scale_workaround=True) + point=QPointF(50, 200), render_mask=True, enable_scale_workaround=True)) def testHtmlFormattingShadow(self): format = QgsTextFormat() @@ -3862,9 +3881,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_shadow', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingBufferShadow(self): format = QgsTextFormat() @@ -3880,9 +3899,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer_shadow', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingVertical(self): format = QgsTextFormat() @@ -3892,9 +3911,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_formatting_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_vertical', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlFormattingBufferVertical(self): format = QgsTextFormat() @@ -3907,9 +3926,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_formatting_buffer_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_formatting_buffer_vertical', None, text=[ 'test'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormatting(self): format = QgsTextFormat() @@ -3918,9 +3937,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricLineHeight(self): format = QgsTextFormat() @@ -3930,9 +3949,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) format.setLineHeight(0.5) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_line_height', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_line_height', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBuffer(self): format = QgsTextFormat() @@ -3944,9 +3963,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingShadow(self): format = QgsTextFormat() @@ -3959,9 +3978,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBufferShadow(self): format = QgsTextFormat() @@ -3977,9 +3996,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingVertical(self): format = QgsTextFormat() @@ -3989,9 +4008,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setColor(QColor(0, 255, 0)) format.setAllowHtmlFormatting(True) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBufferVertical(self): format = QgsTextFormat() @@ -4004,9 +4023,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingShadowVertical(self): format = QgsTextFormat() @@ -4020,9 +4039,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_shadow_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlMixedMetricFormattingBufferShadowVertical(self): format = QgsTextFormat() @@ -4039,9 +4058,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow_vertical', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_mixed_metric_formatting_buffer_shadow_vertical', None, text=[ 'te

st'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlHeadings(self): format = QgsTextFormat() @@ -4050,9 +4069,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_headings', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_headings', None, text=[ '

h1

h2

h3

h4

h5
h6
'], - point=QPointF(10, 300)) + point=QPointF(10, 300))) def testHtmlHeadingsLargerFont(self): format = QgsTextFormat() @@ -4061,9 +4080,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_headings_larger', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_headings_larger', None, text=[ '

h1

h2

h3

h4

h5
h6
'], - point=QPointF(10, 350)) + point=QPointF(10, 350))) def testHtmlAlignmentLeftBase(self): format = QgsTextFormat() @@ -4072,9 +4091,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRender(format, 'html_align_rect_left_base', None, text=[ + self.assertTrue(self.checkRender(format, 'html_align_rect_left_base', None, text=[ '

Test some text

Short

test

test

center
'], - rect=QRectF(10, 10, 300, 300)) + rect=QRectF(10, 10, 300, 300))) def testHtmlAlignmentRightBase(self): format = QgsTextFormat() @@ -4083,9 +4102,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRender(format, 'html_align_rect_right_base', None, text=[ + self.assertTrue(self.checkRender(format, 'html_align_rect_right_base', None, text=[ '

Test some text

Short

test

test

center
'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Right) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Right)) def testHtmlAlignmentCenterBase(self): format = QgsTextFormat() @@ -4094,9 +4113,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRender(format, 'html_align_rect_center_base', None, text=[ + self.assertTrue(self.checkRender(format, 'html_align_rect_center_base', None, text=[ '

Test some text

Short

test

test

center
'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageAutoSize(self): format = QgsTextFormat() @@ -4112,9 +4131,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_autosize', None, text=[ + self.assertTrue(self.checkRender(format, 'image_autosize', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageAutoWidth(self): format = QgsTextFormat() @@ -4130,9 +4149,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_autowidth', None, text=[ + self.assertTrue(self.checkRender(format, 'image_autowidth', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageAutoHeight(self): format = QgsTextFormat() @@ -4148,9 +4167,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_autoheight', None, text=[ + self.assertTrue(self.checkRender(format, 'image_autoheight', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlImageFixedSize(self): format = QgsTextFormat() @@ -4166,9 +4185,9 @@ class PyQgsTextRenderer(QgisTestCase): format.background().setSizeUnit(QgsUnitTypes.RenderUnit.RenderMillimeters) format.background().setFillColor(QColor(255, 255, 255)) - assert self.checkRender(format, 'image_fixed_size', None, text=[ + self.assertTrue(self.checkRender(format, 'image_fixed_size', None, text=[ f'

Test test

'], - rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center) + rect=QRectF(10, 10, 300, 300), alignment=Qgis.TextHorizontalAlignment.Center)) def testHtmlSuperSubscript(self): format = QgsTextFormat() @@ -4177,9 +4196,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_supersubscript', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptFixedFontSize(self): format = QgsTextFormat() @@ -4188,9 +4207,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'text_html_supersubscript_fixed_font_size', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_fixed_font_size', None, text=[ 'suNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptBuffer(self): format = QgsTextFormat() @@ -4202,9 +4221,9 @@ class PyQgsTextRenderer(QgisTestCase): format.buffer().setEnabled(True) format.buffer().setSize(5) format.buffer().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_supersubscript_buffer', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_buffer', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptShadow(self): format = QgsTextFormat() @@ -4217,9 +4236,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_supersubscript_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_shadow', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlSuperSubscriptBufferShadow(self): format = QgsTextFormat() @@ -4235,9 +4254,9 @@ class PyQgsTextRenderer(QgisTestCase): format.shadow().setOffsetDistance(5) format.shadow().setBlurRadius(0) format.shadow().setColor(QColor(50, 150, 200)) - assert self.checkRenderPoint(format, 'text_html_supersubscript_buffer_shadow', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'text_html_supersubscript_buffer_shadow', None, text=[ 'subNsup'], - point=QPointF(50, 200)) + point=QPointF(50, 200))) def testHtmlWordSpacing(self): format = QgsTextFormat() @@ -4246,9 +4265,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_word_spacing', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_word_spacing', None, text=[ 'test of wo space'], - point=QPointF(10, 200)) + point=QPointF(10, 200))) def testHtmlWordSpacingPx(self): format = QgsTextFormat() @@ -4259,9 +4278,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setAllowHtmlFormatting(True) # unit should be ignored, we always treat it as pt as pixels don't # scale - assert self.checkRenderPoint(format, 'html_word_spacing', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_word_spacing', None, text=[ 'test of wo space'], - point=QPointF(10, 200)) + point=QPointF(10, 200))) def testHtmlWordSpacingNegative(self): format = QgsTextFormat() @@ -4270,9 +4289,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setColor(QColor(255, 0, 0)) format.setAllowHtmlFormatting(True) - assert self.checkRenderPoint(format, 'html_word_spacing_negative', None, text=[ + self.assertTrue(self.checkRenderPoint(format, 'html_word_spacing_negative', None, text=[ 'test of wo space'], - point=QPointF(10, 200)) + point=QPointF(10, 200))) def testTextRenderFormat(self): format = QgsTextFormat() @@ -4358,8 +4377,8 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRender(format, 'text_vertical_rect_mode', QgsTextRenderer.TextPart.Text, text=['1234'], - rect=QRectF(40, 20, 350, 350)) + self.assertTrue(self.checkRender(format, 'text_vertical_rect_mode', QgsTextRenderer.TextPart.Text, text=['1234'], + rect=QRectF(40, 20, 350, 350))) def testDrawTextVerticalRectModeCenterAligned(self): format = QgsTextFormat() @@ -4367,9 +4386,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRender(format, 'text_vertical_rect_mode_center_aligned', QgsTextRenderer.TextPart.Text, - text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), - alignment=QgsTextRenderer.HAlignment.AlignCenter) + self.assertTrue(self.checkRender(format, 'text_vertical_rect_mode_center_aligned', QgsTextRenderer.TextPart.Text, + text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), + alignment=QgsTextRenderer.HAlignment.AlignCenter)) def testDrawTextVerticalRectModeRightAligned(self): format = QgsTextFormat() @@ -4377,9 +4396,9 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRender(format, 'text_vertical_rect_mode_right_aligned', QgsTextRenderer.TextPart.Text, - text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRender(format, 'text_vertical_rect_mode_right_aligned', QgsTextRenderer.TextPart.Text, + text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), + alignment=QgsTextRenderer.HAlignment.AlignRight)) def testDrawTextVerticalPointMode(self): format = QgsTextFormat() @@ -4387,8 +4406,8 @@ class PyQgsTextRenderer(QgisTestCase): format.setSize(60) format.setSizeUnit(QgsUnitTypes.RenderUnit.RenderPoints) format.setOrientation(QgsTextFormat.TextOrientation.VerticalOrientation) - assert self.checkRenderPoint(format, 'text_vertical_point_mode', QgsTextRenderer.TextPart.Text, text=['1234', '5678'], - point=QPointF(40, 380)) + self.assertTrue(self.checkRenderPoint(format, 'text_vertical_point_mode', QgsTextRenderer.TextPart.Text, text=['1234', '5678'], + point=QPointF(40, 380))) def testDrawTextOnLineAtStart(self): format = QgsTextFormat() @@ -4663,9 +4682,9 @@ class PyQgsTextRenderer(QgisTestCase): QgsProperty.fromExpression('90*1.5') ) - assert self.checkRender(format, 'datadefined_render', None, - text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), - alignment=QgsTextRenderer.HAlignment.AlignRight) + self.assertTrue(self.checkRender(format, 'datadefined_render', None, + text=['1234', '5678'], rect=QRectF(40, 20, 350, 350), + alignment=QgsTextRenderer.HAlignment.AlignRight)) if __name__ == '__main__': diff --git a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-2.obj b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-2.obj index 099ce35f790..6c872577d83 100644 --- a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-2.obj +++ b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-2.obj @@ -542,29 +542,29 @@ vn 0 1 0 v -0.0224 -0.048 0.197 vn 0 1 0 v -0.0297 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 1 0 0.00114 +vn 1 0 0.00115 v -0.026 -0.048 0.197 -vn 1 0 0.00114 +vn 1 0 0.00115 v -0.026 -0.0534 0.193 -vn 1 0 0.00114 +vn 1 0 0.00115 v -0.026 -0.0534 0.193 -vn 1 0 0.00114 +vn 1 0 0.00115 v -0.026 -0.048 0.197 -vn 1 0 0.00114 +vn 1 0 0.00115 v -0.026 -0.0534 0.197 -vn 1 0 0.00114 +vn 1 0 0.00115 v -0.026 -0.048 0.197 vn 0.0015 0 -1 v -0.0224 -0.048 0.197 @@ -578,17 +578,17 @@ vn 0.0015 0 -1 v -0.0224 -0.0534 0.197 vn 0.0015 0 -1 v -0.0224 -0.048 0.197 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.048 0.201 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.0534 0.197 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.0534 0.197 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.048 0.201 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.0534 0.201 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.048 0.201 vn -0.00121 0 1 v -0.0285 -0.048 0.201 @@ -614,29 +614,29 @@ vn -1 0 -0.000812 v -0.0285 -0.0534 0.196 vn -1 0 -0.000812 v -0.0285 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.048 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000921 v -0.0297 -0.048 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000921 v -0.0297 -0.0534 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000921 v -0.0297 -0.0534 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000921 v -0.0297 -0.048 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000921 v -0.0297 -0.0534 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000921 v -0.0338 -0.0496 0.197 vn 0 1 0 v -0.0374 -0.0496 0.197 @@ -674,17 +674,17 @@ vn 0 1 0 v -0.0297 -0.0496 0.193 vn 0 1 0 v -0.0297 -0.0496 0.193 -vn 1 0 0.00092 +vn 1 0 0.000921 v -0.0297 -0.0496 0.196 -vn 1 0 0.00092 +vn 1 0 0.000921 v -0.0297 -0.0551 0.193 -vn 1 0 0.00092 +vn 1 0 0.000921 v -0.0297 -0.0551 0.193 -vn 1 0 0.00092 +vn 1 0 0.000921 v -0.0297 -0.0496 0.196 -vn 1 0 0.00092 +vn 1 0 0.000921 v -0.0297 -0.0551 0.196 -vn 1 0 0.00092 +vn 1 0 0.000921 v -0.0297 -0.0496 0.196 vn -0.00321 0 1 v -0.0313 -0.0496 0.196 @@ -722,29 +722,29 @@ vn -0.00126 0 1 v -0.0374 -0.0551 0.201 vn -0.00126 0 1 v -0.0374 -0.0496 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.00097 v -0.0374 -0.0496 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.00097 v -0.0374 -0.0551 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.00097 v -0.0374 -0.0551 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.00097 v -0.0374 -0.0496 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.00097 v -0.0374 -0.0551 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.00097 v -0.0374 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 @@ -758,17 +758,17 @@ vn -1 0 -0.00115 v -0.0338 -0.0551 0.193 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0524 -0.0529 0.201 vn 0 1 0 v -0.0524 -0.0529 0.207 @@ -794,17 +794,17 @@ vn 0 1 0 v -0.044 -0.0529 0.204 vn 0 1 0 v -0.0524 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 vn 1 0 0.0011 v -0.0485 -0.0529 0.204 @@ -842,29 +842,29 @@ vn 1 0 0.00162 v -0.044 -0.0583 0.207 vn 1 0 0.00162 v -0.044 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000635 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000635 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000635 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000635 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000635 0 1 v -0.0524 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000635 0 1 v -0.0524 -0.0529 0.207 -vn -1 0 -0.00134 +vn -1 0 -0.00135 v -0.0524 -0.0529 0.201 -vn -1 0 -0.00134 +vn -1 0 -0.00135 v -0.0524 -0.0583 0.207 -vn -1 0 -0.00134 +vn -1 0 -0.00135 v -0.0524 -0.0583 0.207 -vn -1 0 -0.00134 +vn -1 0 -0.00135 v -0.0524 -0.0529 0.201 -vn -1 0 -0.00134 +vn -1 0 -0.00135 v -0.0524 -0.0583 0.201 -vn -1 0 -0.00134 +vn -1 0 -0.00135 v -0.0514 -0.0513 0.196 vn 0 1 0 v -0.0524 -0.0513 0.196 @@ -1052,17 +1052,17 @@ vn 0 1 0 v -0.0463 -0.0513 0.186 vn 0 1 0 v -0.0513 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 vn -0.0182 0 -1 v -0.0464 -0.0513 0.182 @@ -1286,17 +1286,17 @@ vn -0.0137 0 1 v -0.0331 -0.0534 0.186 vn -0.0137 0 1 v -0.0331 -0.048 0.186 -vn -1 0 0.00109 +vn -1 0 0.0011 v -0.0331 -0.048 0.184 -vn -1 0 0.00109 +vn -1 0 0.0011 v -0.0331 -0.0534 0.186 -vn -1 0 0.00109 +vn -1 0 0.0011 v -0.0331 -0.0534 0.186 -vn -1 0 0.00109 +vn -1 0 0.0011 v -0.0331 -0.048 0.184 -vn -1 0 0.00109 +vn -1 0 0.0011 v -0.0331 -0.0534 0.184 -vn -1 0 0.00109 +vn -1 0 0.0011 v -0.0331 -0.048 0.184 vn -0.026 0 -1 v -0.0319 -0.048 0.184 @@ -1718,17 +1718,17 @@ vn 0 1 0 v -0.0361 -0.048 0.17 vn 0 1 0 v -0.0393 -0.048 0.165 -vn 0.00537 0 -1 +vn 0.00538 0 -1 v -0.0362 -0.048 0.165 -vn 0.00537 0 -1 +vn 0.00538 0 -1 v -0.0393 -0.0534 0.165 -vn 0.00537 0 -1 +vn 0.00538 0 -1 v -0.0393 -0.0534 0.165 -vn 0.00537 0 -1 +vn 0.00538 0 -1 v -0.0362 -0.048 0.165 -vn 0.00537 0 -1 +vn 0.00538 0 -1 v -0.0362 -0.0534 0.165 -vn 0.00537 0 -1 +vn 0.00538 0 -1 v -0.0362 -0.048 0.165 vn 1 0 -0.0167 v -0.0361 -0.048 0.17 @@ -1742,17 +1742,17 @@ vn 1 0 -0.0167 v -0.0361 -0.0534 0.17 vn 1 0 -0.0167 v -0.0361 -0.048 0.17 -vn -0.00537 0 1 +vn -0.00538 0 1 v -0.0392 -0.048 0.17 -vn -0.00537 0 1 +vn -0.00538 0 1 v -0.0361 -0.0534 0.17 -vn -0.00537 0 1 +vn -0.00538 0 1 v -0.0361 -0.0534 0.17 -vn -0.00537 0 1 +vn -0.00538 0 1 v -0.0392 -0.048 0.17 -vn -0.00537 0 1 +vn -0.00538 0 1 v -0.0392 -0.0534 0.17 -vn -0.00537 0 1 +vn -0.00538 0 1 v -0.0392 -0.048 0.17 vn -1 0 0.0167 v -0.0393 -0.048 0.165 @@ -1838,17 +1838,17 @@ vn 0 1 0 v -0.0423 -0.048 0.17 vn 0 1 0 v -0.0453 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 @@ -1862,17 +1862,17 @@ vn 1 0 -0.0167 v -0.0423 -0.0534 0.17 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 vn -1 0 0.0167 v -0.0453 -0.048 0.165 @@ -2882,17 +2882,17 @@ vn -0.0308 0 1 v -0.237 -0.0502 0.15 vn -0.0308 0 1 v -0.237 -0.0447 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0447 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0447 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.141 -0.0447 0.109 vn 0 1 0 v -0.141 -0.0447 0.112 @@ -3104,17 +3104,17 @@ vn 0.0296 0 -1 v -0.237 -0.0502 0.147 vn 0.0296 0 -1 v -0.237 -0.0447 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 vn -0.0294 0 1 v -0.241 -0.0447 0.15 @@ -3740,17 +3740,17 @@ vn 0.0724 0 -0.997 v -0.153 -0.0583 0.144 vn 0.0724 0 -0.997 v -0.153 -0.0529 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 vn -0.088 0 0.996 v -0.172 -0.0529 0.146 @@ -3908,17 +3908,17 @@ vn 0.0399 0 -0.999 v -0.0875 -0.0583 0.151 vn 0.0399 0 -0.999 v -0.0875 -0.0529 0.151 -vn 0.999 0 0.0397 +vn 0.999 0 0.0398 v -0.0876 -0.0529 0.155 -vn 0.999 0 0.0397 +vn 0.999 0 0.0398 v -0.0875 -0.0583 0.151 -vn 0.999 0 0.0397 +vn 0.999 0 0.0398 v -0.0875 -0.0583 0.151 -vn 0.999 0 0.0397 +vn 0.999 0 0.0398 v -0.0876 -0.0529 0.155 -vn 0.999 0 0.0397 +vn 0.999 0 0.0398 v -0.0876 -0.0583 0.155 -vn 0.999 0 0.0397 +vn 0.999 0 0.0398 v -0.0876 -0.0529 0.155 vn -0.0399 0 0.999 v -0.0942 -0.0529 0.155 @@ -4196,17 +4196,17 @@ vn 0 1 0 v -0.188 -0.0496 0.16 vn 0 1 0 v -0.191 -0.0496 0.155 -vn -0.0199 0 -1 +vn -0.0198 0 -1 v -0.188 -0.0496 0.155 -vn -0.0199 0 -1 +vn -0.0198 0 -1 v -0.191 -0.0551 0.155 -vn -0.0199 0 -1 +vn -0.0198 0 -1 v -0.191 -0.0551 0.155 -vn -0.0199 0 -1 +vn -0.0198 0 -1 v -0.188 -0.0496 0.155 -vn -0.0199 0 -1 +vn -0.0198 0 -1 v -0.188 -0.0551 0.155 -vn -0.0199 0 -1 +vn -0.0198 0 -1 v -0.188 -0.0496 0.155 vn 1 0 -0.0206 v -0.188 -0.0496 0.16 @@ -4220,17 +4220,17 @@ vn 1 0 -0.0206 v -0.188 -0.0551 0.16 vn 1 0 -0.0206 v -0.188 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 vn -1 0 0.0206 v -0.191 -0.0496 0.155 @@ -4268,17 +4268,17 @@ vn -0.998 0 -0.0581 v -0.247 -0.0502 0.149 vn -0.998 0 -0.0581 v -0.247 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 vn 0.998 0 0.057 v -0.246 -0.0447 0.152 @@ -5708,29 +5708,29 @@ vn -0.0144 0 -1 v -0.301 -0.0583 0.141 vn -0.0144 0 -1 v -0.301 -0.0529 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000497 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000497 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000497 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000497 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000497 0 1 v -0.302 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000497 0 1 v -0.302 -0.0529 0.145 vn 1 0 -0.0275 v -0.302 -0.0529 0.147 @@ -8246,17 +8246,17 @@ vn 0.474 0 -0.88 v -0.307 -0.0682 0.276 vn 0.474 0 -0.88 v -0.307 -0.0627 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0682 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 vn 0.883 0 0.47 v -0.309 -0.0627 0.28 @@ -8462,17 +8462,17 @@ vn 0.879 0 0.476 v -0.291 -0.0714 0.29 vn 0.879 0 0.476 v -0.291 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 vn -0.474 0 0.88 v -0.294 -0.066 0.289 @@ -8690,17 +8690,17 @@ vn 0.471 0 -0.882 v -0.285 -0.0714 0.289 vn 0.471 0 -0.882 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 vn 0.475 0 -0.88 v -0.283 -0.066 0.291 @@ -11036,29 +11036,29 @@ vn 0 1 0 v 0.00615 -0.0398 0.16 vn 0 1 0 v 0.000675 -0.0398 0.164 -vn -1 0 -0.00085 +vn -1 0 -0.000848 v 0.00068 -0.0398 0.158 -vn -1 0 -0.00085 +vn -1 0 -0.000848 v 0.000675 -0.0453 0.164 -vn -1 0 -0.00085 +vn -1 0 -0.000848 v 0.000675 -0.0453 0.164 -vn -1 0 -0.00085 +vn -1 0 -0.000848 v 0.00068 -0.0398 0.158 -vn -1 0 -0.00085 +vn -1 0 -0.000848 v 0.00068 -0.0453 0.158 -vn -1 0 -0.00085 +vn -1 0 -0.000848 v 0.00068 -0.0398 0.158 -vn 0.00159 0 -1 +vn 0.0016 0 -1 v 0.00247 -0.0398 0.158 -vn 0.00159 0 -1 +vn 0.0016 0 -1 v 0.00068 -0.0453 0.158 -vn 0.00159 0 -1 +vn 0.0016 0 -1 v 0.00068 -0.0453 0.158 -vn 0.00159 0 -1 +vn 0.0016 0 -1 v 0.00247 -0.0398 0.158 -vn 0.00159 0 -1 +vn 0.0016 0 -1 v 0.00247 -0.0453 0.158 -vn 0.00159 0 -1 +vn 0.0016 0 -1 v 0.00247 -0.0398 0.158 vn -1 0 0.00135 v 0.00247 -0.0398 0.157 @@ -11108,17 +11108,17 @@ vn -0.00709 0 1 v 0.0055 -0.0453 0.158 vn -0.00709 0 1 v 0.0055 -0.0398 0.158 -vn 1 0 0.000833 +vn 1 0 0.000842 v 0.0055 -0.0398 0.16 -vn 1 0 0.000833 +vn 1 0 0.000842 v 0.0055 -0.0453 0.158 -vn 1 0 0.000833 +vn 1 0 0.000842 v 0.0055 -0.0453 0.158 -vn 1 0 0.000833 +vn 1 0 0.000842 v 0.0055 -0.0398 0.16 -vn 1 0 0.000833 +vn 1 0 0.000842 v 0.0055 -0.0453 0.16 -vn 1 0 0.000833 +vn 1 0 0.000842 v 0.0055 -0.0398 0.16 vn -0.00351 0 -1 v 0.00615 -0.0398 0.16 @@ -11132,17 +11132,17 @@ vn -0.00351 0 -1 v 0.00615 -0.0453 0.16 vn -0.00351 0 -1 v 0.00615 -0.0398 0.16 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0398 0.164 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0453 0.16 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0453 0.16 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0398 0.164 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0453 0.164 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0398 0.164 vn -0.00331 0 1 v 0.0041 -0.0398 0.164 @@ -11210,17 +11210,17 @@ vn -1 0 -0.00363 v -0.000303 -0.0469 0.164 vn -1 0 -0.00363 v -0.000303 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 vn 0.00276 0 -1 v 0.0041 -0.0414 0.164 @@ -11258,17 +11258,17 @@ vn -0.00412 0 1 v 0.000774 -0.0469 0.171 vn -0.00412 0 1 v 0.000774 -0.0414 0.171 -vn -1 0 -0.00458 +vn -1 0 -0.00459 v 0.000782 -0.0414 0.169 -vn -1 0 -0.00458 +vn -1 0 -0.00459 v 0.000774 -0.0469 0.171 -vn -1 0 -0.00458 +vn -1 0 -0.00459 v 0.000774 -0.0469 0.171 -vn -1 0 -0.00458 +vn -1 0 -0.00459 v 0.000782 -0.0414 0.169 -vn -1 0 -0.00458 +vn -1 0 -0.00459 v 0.000782 -0.0469 0.169 -vn -1 0 -0.00458 +vn -1 0 -0.00459 v 0.000782 -0.0414 0.169 vn -0.00417 0 1 v -0.000323 -0.0414 0.169 @@ -14024,17 +14024,17 @@ vn 0 1 0 v -0.248 -0.0136 -0.0633 vn 0 1 0 v -0.259 -0.0136 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 vn 0.0152 0 -1 v -0.248 -0.0136 -0.0655 @@ -16760,17 +16760,17 @@ vn -0.0937 0 0.996 v 0.0522 -0.0518 0.188 vn -0.0937 0 0.996 v 0.0522 -0.0463 0.188 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0463 0.18 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.0522 -0.0518 0.188 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.0522 -0.0518 0.188 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0463 0.18 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0518 0.18 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0463 0.18 vn 0.0937 0 -0.996 v 0.0583 -0.0463 0.18 @@ -16856,41 +16856,41 @@ vn -1 0 -0.00929 v 0.0624 -0.0583 0.154 vn -1 0 -0.00929 v 0.0624 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -1 0 -0.00721 +vn -1 0 -0.0072 v 0.0615 -0.0529 0.153 -vn -1 0 -0.00721 +vn -1 0 -0.0072 v 0.0615 -0.0583 0.154 -vn -1 0 -0.00721 +vn -1 0 -0.0072 v 0.0615 -0.0583 0.154 -vn -1 0 -0.00721 +vn -1 0 -0.0072 v 0.0615 -0.0529 0.153 -vn -1 0 -0.00721 +vn -1 0 -0.0072 v 0.0615 -0.0583 0.153 -vn -1 0 -0.00721 +vn -1 0 -0.0072 v 0.0615 -0.0529 0.153 -vn 0.00824 0 -1 +vn 0.00825 0 -1 v 0.0641 -0.0529 0.153 -vn 0.00824 0 -1 +vn 0.00825 0 -1 v 0.0615 -0.0583 0.153 -vn 0.00824 0 -1 +vn 0.00825 0 -1 v 0.0615 -0.0583 0.153 -vn 0.00824 0 -1 +vn 0.00825 0 -1 v 0.0641 -0.0529 0.153 -vn 0.00824 0 -1 +vn 0.00825 0 -1 v 0.0641 -0.0583 0.153 -vn 0.00824 0 -1 +vn 0.00825 0 -1 v 0.0641 -0.0529 0.153 vn 1 0 0.00813 v 0.0641 -0.0529 0.154 @@ -18020,17 +18020,17 @@ vn -0.0107 0 -1 v 0.217 -0.0583 0.449 vn -0.0107 0 -1 v 0.217 -0.0529 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 vn -0.0197 0 1 v 0.213 -0.0529 0.458 @@ -18056,17 +18056,17 @@ vn -1 0 -0.00435 v 0.213 -0.0583 0.454 vn -1 0 -0.00435 v 0.213 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 vn -1 0 0.019 v 0.207 -0.0529 0.444 @@ -19832,29 +19832,29 @@ vn 1 0 0.0151 v 0.281 -0.0142 0.0868 vn 1 0 0.0151 v 0.281 -0.00872 0.0868 -vn 0.0151 0 -1 +vn 0.015 0 -1 v 0.282 -0.00872 0.0868 -vn 0.0151 0 -1 +vn 0.015 0 -1 v 0.281 -0.0142 0.0868 -vn 0.0151 0 -1 +vn 0.015 0 -1 v 0.281 -0.0142 0.0868 -vn 0.0151 0 -1 +vn 0.015 0 -1 v 0.282 -0.00872 0.0868 -vn 0.0151 0 -1 +vn 0.015 0 -1 v 0.282 -0.0142 0.0868 -vn 0.0151 0 -1 +vn 0.015 0 -1 v 0.282 -0.00872 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 vn 0.0151 0 -1 v 0.285 -0.00872 0.0879 @@ -21098,17 +21098,17 @@ vn 0.628 0 0.778 v 0.422 0.0382 0.0445 vn 0.628 0 0.778 v 0.422 0.0436 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0382 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.233 -0.0136 0.0844 vn 0 1 0 v 0.236 -0.0136 0.0825 @@ -22472,17 +22472,17 @@ vn 0 1 0 v 0.276 -0.00872 0.0908 vn 0 1 0 v 0.272 -0.00872 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.0142 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 vn 0.0461 0 -0.999 v 0.276 -0.00872 0.0908 @@ -23534,17 +23534,17 @@ vn 0.99 0 -0.142 v 0.135 -0.0436 0.0724 vn 0.99 0 -0.142 v 0.135 -0.0382 0.0724 -vn -0.00889 0 1 +vn -0.0089 0 1 v 0.132 -0.0382 0.0724 -vn -0.00889 0 1 +vn -0.0089 0 1 v 0.135 -0.0436 0.0724 -vn -0.00889 0 1 +vn -0.0089 0 1 v 0.135 -0.0436 0.0724 -vn -0.00889 0 1 +vn -0.0089 0 1 v 0.132 -0.0382 0.0724 -vn -0.00889 0 1 +vn -0.0089 0 1 v 0.132 -0.0436 0.0724 -vn -0.00889 0 1 +vn -0.0089 0 1 v 0.132 -0.0382 0.0724 vn -0.996 0 0.0942 v 0.132 -0.0382 0.0662 @@ -26552,17 +26552,17 @@ vn -0.34 0 -0.94 v 0.0605 -0.0534 0.101 vn -0.34 0 -0.94 v 0.0605 -0.048 0.101 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.048 0.104 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0605 -0.0534 0.101 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0605 -0.0534 0.101 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.048 0.104 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.0534 0.104 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.048 0.104 vn 0.391 0 0.92 v 0.0572 -0.048 0.106 @@ -27782,17 +27782,17 @@ vn 0.877 0 0.481 v 0.194 -0.0354 0.121 vn 0.877 0 0.481 v 0.194 -0.03 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.0354 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 vn 0.877 0 0.481 v 0.194 -0.03 0.126 @@ -31970,17 +31970,17 @@ vn 0.538 0 0.843 v 0.258 -0.0485 0.36 vn 0.538 0 0.843 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0431 0.36 vn -0.854 0 0.52 v 0.258 -0.0431 0.36 @@ -33464,17 +33464,17 @@ vn -0.0103 0 -1 v 0.0681 -0.0567 0.133 vn -0.0103 0 -1 v 0.0681 -0.0513 0.133 -vn 1 0 -0.0106 +vn 1 0 -0.0105 v 0.0682 -0.0513 0.138 -vn 1 0 -0.0106 +vn 1 0 -0.0105 v 0.0681 -0.0567 0.133 -vn 1 0 -0.0106 +vn 1 0 -0.0105 v 0.0681 -0.0567 0.133 -vn 1 0 -0.0106 +vn 1 0 -0.0105 v 0.0682 -0.0513 0.138 -vn 1 0 -0.0106 +vn 1 0 -0.0105 v 0.0682 -0.0567 0.138 -vn 1 0 -0.0106 +vn 1 0 -0.0105 v 0.0682 -0.0513 0.138 vn 1 0 -0.0114 v 0.0682 -0.0513 0.144 @@ -33770,17 +33770,17 @@ vn -0.998 0 -0.0597 v 0.0956 -0.0567 0.138 vn -0.998 0 -0.0597 v 0.0956 -0.0513 0.138 -vn -1 0 -0.0165 +vn -1 0 -0.0164 v 0.0956 -0.0513 0.135 -vn -1 0 -0.0165 +vn -1 0 -0.0164 v 0.0956 -0.0567 0.138 -vn -1 0 -0.0165 +vn -1 0 -0.0164 v 0.0956 -0.0567 0.138 -vn -1 0 -0.0165 +vn -1 0 -0.0164 v 0.0956 -0.0513 0.135 -vn -1 0 -0.0165 +vn -1 0 -0.0164 v 0.0956 -0.0567 0.135 -vn -1 0 -0.0165 +vn -1 0 -0.0164 v 0.107 -0.048 0.135 vn 0 1 0 v 0.104 -0.048 0.145 @@ -34490,17 +34490,17 @@ vn 0.582 0 -0.813 v 0.146 -0.0485 0.17 vn 0.582 0 -0.813 v 0.146 -0.0431 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0485 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 vn -0.582 0 0.813 v 0.142 -0.0431 0.17 @@ -36128,17 +36128,17 @@ vn 1 0 -0.0108 v 0.0707 -0.0583 0.152 vn 1 0 -0.0108 v 0.0707 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 vn -1 0 -0.0151 v 0.0673 -0.0529 0.151 @@ -37340,41 +37340,41 @@ vn -1 0 0.00184 v 0.227 -0.06 0.444 vn -1 0 0.00184 v 0.227 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.227 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.227 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000737 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000737 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000737 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000737 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000737 0 -1 v 0.232 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000737 0 -1 v 0.232 -0.0545 0.439 vn 1 0 -0.00192 v 0.232 -0.0545 0.443 @@ -37412,29 +37412,29 @@ vn 1 0 0.00107 v 0.231 -0.06 0.444 vn 1 0 0.00107 v 0.231 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00207 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00207 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00207 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00207 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00207 0 -1 v 0.233 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00207 0 -1 v 0.233 -0.0545 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 vn -0.0268 0 1 v 0.232 -0.0545 0.445 @@ -37970,17 +37970,17 @@ vn 0.0719 0 0.997 v 0.101 -0.0567 0.172 vn 0.0719 0 0.997 v 0.101 -0.0513 0.172 -vn 0.997 0 -0.0719 +vn 0.997 0 -0.072 v 0.101 -0.0513 0.173 -vn 0.997 0 -0.0719 +vn 0.997 0 -0.072 v 0.101 -0.0567 0.172 -vn 0.997 0 -0.0719 +vn 0.997 0 -0.072 v 0.101 -0.0567 0.172 -vn 0.997 0 -0.0719 +vn 0.997 0 -0.072 v 0.101 -0.0513 0.173 -vn 0.997 0 -0.0719 +vn 0.997 0 -0.072 v 0.101 -0.0567 0.173 -vn 0.997 0 -0.0719 +vn 0.997 0 -0.072 v 0.101 -0.0513 0.173 vn 0.0719 0 0.997 v 0.0927 -0.0513 0.174 @@ -38864,17 +38864,17 @@ vn -0.999 0 0.0368 v 0.0346 -0.0502 0.366 vn -0.999 0 0.0368 v 0.0346 -0.0447 0.366 -vn -0.0367 0 -0.999 +vn -0.0366 0 -0.999 v 0.0352 -0.0447 0.366 -vn -0.0367 0 -0.999 +vn -0.0366 0 -0.999 v 0.0346 -0.0502 0.366 -vn -0.0367 0 -0.999 +vn -0.0366 0 -0.999 v 0.0346 -0.0502 0.366 -vn -0.0367 0 -0.999 +vn -0.0366 0 -0.999 v 0.0352 -0.0447 0.366 -vn -0.0367 0 -0.999 +vn -0.0366 0 -0.999 v 0.0352 -0.0502 0.366 -vn -0.0367 0 -0.999 +vn -0.0366 0 -0.999 v 0.0352 -0.0447 0.366 vn -0.999 0 0.0377 v 0.0351 -0.0447 0.363 diff --git a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-3.obj b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-3.obj index 243232c91ca..df283ca4cfe 100644 --- a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-3.obj +++ b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-3.obj @@ -1280,17 +1280,17 @@ vn 0.474 0 -0.88 v -0.307 -0.0682 0.276 vn 0.474 0 -0.88 v -0.307 -0.0627 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0682 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 vn 0.883 0 0.47 v -0.309 -0.0627 0.28 @@ -1496,17 +1496,17 @@ vn 0.879 0 0.476 v -0.291 -0.0714 0.29 vn 0.879 0 0.476 v -0.291 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 vn -0.474 0 0.88 v -0.294 -0.066 0.289 @@ -1724,17 +1724,17 @@ vn 0.471 0 -0.882 v -0.285 -0.0714 0.289 vn 0.471 0 -0.882 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 vn 0.475 0 -0.88 v -0.283 -0.066 0.291 @@ -2156,17 +2156,17 @@ vn 0 1 0 v -0.321 -0.0594 0.267 vn 0 1 0 v -0.323 -0.0594 0.268 -vn -0.907 0 -0.42 +vn -0.908 0 -0.42 v -0.322 -0.0594 0.267 -vn -0.907 0 -0.42 +vn -0.908 0 -0.42 v -0.323 -0.0649 0.268 -vn -0.907 0 -0.42 +vn -0.908 0 -0.42 v -0.323 -0.0649 0.268 -vn -0.907 0 -0.42 +vn -0.908 0 -0.42 v -0.322 -0.0594 0.267 -vn -0.907 0 -0.42 +vn -0.908 0 -0.42 v -0.322 -0.0649 0.267 -vn -0.907 0 -0.42 +vn -0.908 0 -0.42 v -0.322 -0.0594 0.267 vn 0.418 0 -0.909 v -0.321 -0.0594 0.267 @@ -2844,17 +2844,17 @@ vn -0.998 0 -0.0581 v -0.247 -0.0502 0.149 vn -0.998 0 -0.0581 v -0.247 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 vn 0.998 0 0.057 v -0.246 -0.0447 0.152 @@ -3066,29 +3066,29 @@ vn -0.0144 0 -1 v -0.301 -0.0583 0.141 vn -0.0144 0 -1 v -0.301 -0.0529 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0529 0.145 vn 1 0 -0.0275 v -0.302 -0.0529 0.147 @@ -4625,17 +4625,17 @@ vn 0 1 0 v -0.248 -0.0136 -0.0633 vn 0 1 0 v -0.259 -0.0136 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 vn 0.0152 0 -1 v -0.248 -0.0136 -0.0655 @@ -7333,17 +7333,17 @@ vn 0 1 0 v -0.0224 -0.048 0.197 vn 0 1 0 v -0.0297 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 vn 1 0 0.00114 v -0.026 -0.048 0.197 @@ -7369,17 +7369,17 @@ vn 0.0015 0 -1 v -0.0224 -0.0534 0.197 vn 0.0015 0 -1 v -0.0224 -0.048 0.197 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.048 0.201 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.0534 0.197 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.0534 0.197 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.048 0.201 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.0534 0.201 -vn 1 0 0.00144 +vn 1 0 0.00145 v -0.0224 -0.048 0.201 vn -0.00121 0 1 v -0.0285 -0.048 0.201 @@ -7393,41 +7393,41 @@ vn -0.00121 0 1 v -0.0285 -0.0534 0.201 vn -0.00121 0 1 v -0.0285 -0.048 0.201 -vn -1 0 -0.000812 +vn -1 0 -0.000811 v -0.0285 -0.048 0.196 -vn -1 0 -0.000812 +vn -1 0 -0.000811 v -0.0285 -0.0534 0.201 -vn -1 0 -0.000812 +vn -1 0 -0.000811 v -0.0285 -0.0534 0.201 -vn -1 0 -0.000812 +vn -1 0 -0.000811 v -0.0285 -0.048 0.196 -vn -1 0 -0.000812 +vn -1 0 -0.000811 v -0.0285 -0.0534 0.196 -vn -1 0 -0.000812 +vn -1 0 -0.000811 v -0.0285 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000606 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000606 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000606 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000606 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000606 0 1 v -0.0297 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000606 0 1 v -0.0297 -0.048 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.048 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.0534 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.0534 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.048 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.0534 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0338 -0.0496 0.197 vn 0 1 0 v -0.0374 -0.0496 0.197 @@ -7465,17 +7465,17 @@ vn 0 1 0 v -0.0297 -0.0496 0.193 vn 0 1 0 v -0.0297 -0.0496 0.193 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0496 0.196 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0551 0.193 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0551 0.193 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0496 0.196 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0551 0.196 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0496 0.196 vn -0.00321 0 1 v -0.0313 -0.0496 0.196 @@ -7525,17 +7525,17 @@ vn -1 0 -0.000968 v -0.0374 -0.0551 0.197 vn -1 0 -0.000968 v -0.0374 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 @@ -7549,17 +7549,17 @@ vn -1 0 -0.00115 v -0.0338 -0.0551 0.193 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0524 -0.0529 0.201 vn 0 1 0 v -0.0524 -0.0529 0.207 @@ -7585,17 +7585,17 @@ vn 0 1 0 v -0.044 -0.0529 0.204 vn 0 1 0 v -0.0524 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 vn 1 0 0.0011 v -0.0485 -0.0529 0.204 @@ -7633,17 +7633,17 @@ vn 1 0 0.00162 v -0.044 -0.0583 0.207 vn 1 0 0.00162 v -0.044 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 vn -1 0 -0.00134 v -0.0524 -0.0529 0.201 @@ -7843,17 +7843,17 @@ vn 0 1 0 v -0.0463 -0.0513 0.186 vn 0 1 0 v -0.0513 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 vn -0.0182 0 -1 v -0.0464 -0.0513 0.182 @@ -8629,17 +8629,17 @@ vn 0 1 0 v -0.0423 -0.048 0.17 vn 0 1 0 v -0.0453 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 @@ -8653,17 +8653,17 @@ vn 1 0 -0.0167 v -0.0423 -0.0534 0.17 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 vn -1 0 0.0167 v -0.0453 -0.048 0.165 @@ -9181,17 +9181,17 @@ vn 0 1 0 v -0.212 -0.048 0.153 vn 0 1 0 v -0.216 -0.048 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.048 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.216 -0.0534 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.216 -0.0534 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.048 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.0534 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.048 0.15 vn 0.999 0 0.0516 v -0.212 -0.048 0.153 @@ -10375,17 +10375,17 @@ vn 0.0724 0 -0.997 v -0.153 -0.0583 0.144 vn 0.0724 0 -0.997 v -0.153 -0.0529 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 vn -0.088 0 0.996 v -0.172 -0.0529 0.146 @@ -10783,17 +10783,17 @@ vn -0.00872 0 -1 v -0.141 -0.0583 0.152 vn -0.00872 0 -1 v -0.141 -0.0529 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 vn 0.0106 0 1 v -0.144 -0.0529 0.155 @@ -10855,17 +10855,17 @@ vn 1 0 -0.0206 v -0.188 -0.0551 0.16 vn 1 0 -0.0206 v -0.188 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 vn -1 0 0.0206 v -0.191 -0.0496 0.155 @@ -13789,17 +13789,17 @@ vn -1 0 0.00135 v 0.00247 -0.0453 0.157 vn -1 0 0.00135 v 0.00247 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00247 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00247 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 vn 1 0 0.00134 v 0.00626 -0.0398 0.158 @@ -13813,29 +13813,29 @@ vn 1 0 0.00134 v 0.00626 -0.0453 0.158 vn 1 0 0.00134 v 0.00626 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.00626 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.00626 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0398 0.16 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0453 0.158 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0453 0.158 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0398 0.16 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0453 0.16 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0398 0.16 vn -0.00351 0 -1 v 0.00615 -0.0398 0.16 @@ -13849,17 +13849,17 @@ vn -0.00351 0 -1 v 0.00615 -0.0453 0.16 vn -0.00351 0 -1 v 0.00615 -0.0398 0.16 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0398 0.164 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0453 0.16 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0453 0.16 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0398 0.164 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0453 0.164 -vn 1 0 0.00094 +vn 1 0 0.000941 v 0.00615 -0.0398 0.164 vn -0.00331 0 1 v 0.0041 -0.0398 0.164 @@ -13927,17 +13927,17 @@ vn -1 0 -0.00363 v -0.000303 -0.0469 0.164 vn -1 0 -0.00363 v -0.000303 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 vn 0.00276 0 -1 v 0.0041 -0.0414 0.164 @@ -15801,9 +15801,9 @@ v -0.00196 -0.0316 -0.119 vn 0 1 0 v -0.000154 -0.0316 -0.114 vn 0 1 0 -v -2.76e-06 -0.0316 -0.119 +v -2.75e-06 -0.0316 -0.119 vn 0 1 0 -v -2.76e-06 -0.0316 -0.119 +v -2.75e-06 -0.0316 -0.119 vn 0 1 0 v -0.000154 -0.0316 -0.114 vn 0 1 0 @@ -15835,23 +15835,23 @@ v -0.00196 -0.0371 -0.119 vn -0.934 0 0.358 v -0.00196 -0.0316 -0.119 vn -0.358 0 -0.934 -v -2.76e-06 -0.0316 -0.119 +v -2.75e-06 -0.0316 -0.119 vn -0.358 0 -0.934 v -0.00196 -0.0371 -0.119 vn -0.358 0 -0.934 v -0.00196 -0.0371 -0.119 vn -0.358 0 -0.934 -v -2.76e-06 -0.0316 -0.119 +v -2.75e-06 -0.0316 -0.119 vn -0.358 0 -0.934 -v -2.76e-06 -0.0371 -0.119 +v -2.75e-06 -0.0371 -0.119 vn -0.358 0 -0.934 -v -2.76e-06 -0.0316 -0.119 +v -2.75e-06 -0.0316 -0.119 vn 0.934 0 -0.358 v 0.00181 -0.0316 -0.115 vn 0.934 0 -0.358 -v -2.76e-06 -0.0371 -0.119 +v -2.75e-06 -0.0371 -0.119 vn 0.934 0 -0.358 -v -2.76e-06 -0.0371 -0.119 +v -2.75e-06 -0.0371 -0.119 vn 0.934 0 -0.358 v 0.00181 -0.0316 -0.115 vn 0.934 0 -0.358 @@ -17082,17 +17082,17 @@ vn -0.0107 0 -1 v 0.217 -0.0583 0.449 vn -0.0107 0 -1 v 0.217 -0.0529 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 vn -0.0197 0 1 v 0.213 -0.0529 0.458 @@ -17118,17 +17118,17 @@ vn -1 0 -0.00435 v 0.213 -0.0583 0.454 vn -1 0 -0.00435 v 0.213 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 vn -1 0 0.019 v 0.207 -0.0529 0.444 @@ -18486,17 +18486,17 @@ vn 0.538 0 0.843 v 0.258 -0.0485 0.36 vn 0.538 0 0.843 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0431 0.36 vn -0.854 0 0.52 v 0.258 -0.0431 0.36 @@ -18828,17 +18828,17 @@ vn 0 1 0 v 0.233 -0.0545 0.446 vn 0 1 0 v 0.227 -0.0545 0.446 -vn -1 0 0.00184 +vn -1 0 0.00185 v 0.227 -0.0545 0.444 -vn -1 0 0.00184 +vn -1 0 0.00185 v 0.227 -0.06 0.446 -vn -1 0 0.00184 +vn -1 0 0.00185 v 0.227 -0.06 0.446 -vn -1 0 0.00184 +vn -1 0 0.00185 v 0.227 -0.0545 0.444 -vn -1 0 0.00184 +vn -1 0 0.00185 v 0.227 -0.06 0.444 -vn -1 0 0.00184 +vn -1 0 0.00185 v 0.227 -0.0545 0.444 vn 0.00115 0 -1 v 0.228 -0.0545 0.444 @@ -18852,29 +18852,29 @@ vn 0.00115 0 -1 v 0.228 -0.06 0.444 vn 0.00115 0 -1 v 0.228 -0.0545 0.444 -vn -1 0 0.00089 +vn -1 0 0.000892 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000892 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000892 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000892 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000892 v 0.228 -0.06 0.439 -vn -1 0 0.00089 +vn -1 0 0.000892 v 0.228 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000735 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000735 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000735 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000735 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000735 0 -1 v 0.232 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000735 0 -1 v 0.232 -0.0545 0.439 vn 1 0 -0.00192 v 0.232 -0.0545 0.443 @@ -18912,29 +18912,29 @@ vn 1 0 0.00107 v 0.231 -0.06 0.444 vn 1 0 0.00107 v 0.231 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 vn -0.0268 0 1 v 0.232 -0.0545 0.445 @@ -20566,17 +20566,17 @@ vn -0.0937 0 0.996 v 0.0522 -0.0518 0.188 vn -0.0937 0 0.996 v 0.0522 -0.0463 0.188 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0463 0.18 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.0522 -0.0518 0.188 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.0522 -0.0518 0.188 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0463 0.18 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0518 0.18 -vn -0.995 0 -0.0948 +vn -0.996 0 -0.0948 v 0.053 -0.0463 0.18 vn 0.0937 0 -0.996 v 0.0583 -0.0463 0.18 @@ -20662,17 +20662,17 @@ vn -1 0 -0.00929 v 0.0624 -0.0583 0.154 vn -1 0 -0.00929 v 0.0624 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 vn -1 0 -0.00721 v 0.0615 -0.0529 0.153 @@ -23746,17 +23746,17 @@ vn -0.951 0 0.31 v 0.107 -0.0518 0.0743 vn -0.951 0 0.31 v 0.107 -0.0463 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.107 -0.0518 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.107 -0.0518 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0518 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 vn -0.985 0 0.175 v 0.108 -0.0463 0.0724 @@ -26014,17 +26014,17 @@ vn -0.34 0 -0.94 v 0.0605 -0.0534 0.101 vn -0.34 0 -0.94 v 0.0605 -0.048 0.101 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.048 0.104 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0605 -0.0534 0.101 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0605 -0.0534 0.101 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.048 0.104 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.0534 0.104 -vn 0.913 0 -0.408 +vn 0.913 0 -0.409 v 0.0616 -0.048 0.104 vn 0.391 0 0.92 v 0.0572 -0.048 0.106 @@ -27112,17 +27112,17 @@ vn 0.877 0 0.481 v 0.194 -0.0354 0.121 vn 0.877 0 0.481 v 0.194 -0.03 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.0354 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 vn 0.877 0 0.481 v 0.194 -0.03 0.126 @@ -29170,17 +29170,17 @@ vn 0.494 0 0.87 v 0.174 -0.0403 0.159 vn 0.494 0 0.87 v 0.174 -0.0349 0.159 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.173 -0.0349 0.157 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.174 -0.0403 0.159 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.174 -0.0403 0.159 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.173 -0.0349 0.157 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.173 -0.0403 0.157 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.258 -0.0284 0.187 vn 0 1 0 v 0.256 -0.0284 0.187 @@ -32356,17 +32356,17 @@ vn 0.582 0 -0.813 v 0.146 -0.0485 0.17 vn 0.582 0 -0.813 v 0.146 -0.0431 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0485 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 vn -0.582 0 0.813 v 0.142 -0.0431 0.17 @@ -33934,17 +33934,17 @@ vn 1 0 -0.0108 v 0.0707 -0.0583 0.152 vn 1 0 -0.0108 v 0.0707 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 vn -1 0 -0.0151 v 0.0673 -0.0529 0.151 @@ -34570,17 +34570,17 @@ vn 0.999 0 0.0411 v 0.0996 -0.0551 0.144 vn 0.999 0 0.0411 v 0.0996 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0996 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0996 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 vn -0.999 0 -0.0426 v 0.0977 -0.0496 0.142 @@ -34642,17 +34642,17 @@ vn -0.096 0 -0.995 v 0.0891 -0.0583 0.155 vn -0.096 0 -0.995 v 0.0891 -0.0529 0.155 -vn 0.995 0 -0.0974 +vn 0.995 0 -0.0973 v 0.0891 -0.0529 0.156 -vn 0.995 0 -0.0974 +vn 0.995 0 -0.0973 v 0.0891 -0.0583 0.155 -vn 0.995 0 -0.0974 +vn 0.995 0 -0.0973 v 0.0891 -0.0583 0.155 -vn 0.995 0 -0.0974 +vn 0.995 0 -0.0973 v 0.0891 -0.0529 0.156 -vn 0.995 0 -0.0974 +vn 0.995 0 -0.0973 v 0.0891 -0.0583 0.156 -vn 0.995 0 -0.0974 +vn 0.995 0 -0.0973 v 0.0891 -0.0529 0.156 vn 0.995 0 -0.0948 v 0.0894 -0.0529 0.159 @@ -37282,17 +37282,17 @@ vn 1 0 0.00875 v 0.0708 -0.0583 0.16 vn 1 0 0.00875 v 0.0708 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0708 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0708 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 vn -1 0 -0.00885 v 0.0685 -0.0529 0.158 @@ -43664,17 +43664,17 @@ vn 0.0151 0 -1 v 0.282 -0.0142 0.0868 vn 0.0151 0 -1 v 0.282 -0.00872 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 vn 0.0151 0 -1 v 0.285 -0.00872 0.0879 @@ -44450,17 +44450,17 @@ vn 0.628 0 0.778 v 0.422 0.0382 0.0445 vn 0.628 0 0.778 v 0.422 0.0436 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0382 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.294 0.006 0.039 vn 0 1 0 v 0.297 0.006 0.0453 @@ -44804,17 +44804,17 @@ vn 0 1 0 v 0.276 -0.00872 0.0908 vn 0 1 0 v 0.272 -0.00872 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.0142 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 vn 0.0461 0 -0.999 v 0.276 -0.00872 0.0908 diff --git a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-4.obj b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-4.obj index 29a72ddf401..cedda100ee9 100644 --- a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-4.obj +++ b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-4.obj @@ -2282,17 +2282,17 @@ vn 0.474 0 -0.88 v -0.307 -0.0682 0.276 vn 0.474 0 -0.88 v -0.307 -0.0627 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0682 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 vn 0.883 0 0.47 v -0.309 -0.0627 0.28 @@ -2498,17 +2498,17 @@ vn 0.879 0 0.476 v -0.291 -0.0714 0.29 vn 0.879 0 0.476 v -0.291 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 vn -0.474 0 0.88 v -0.294 -0.066 0.289 @@ -2726,17 +2726,17 @@ vn 0.471 0 -0.882 v -0.285 -0.0714 0.289 vn 0.471 0 -0.882 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 vn 0.475 0 -0.88 v -0.283 -0.066 0.291 @@ -3586,17 +3586,17 @@ vn -0.0308 0 1 v -0.237 -0.0502 0.15 vn -0.0308 0 1 v -0.237 -0.0447 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0447 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0447 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.244 -0.0447 0.147 vn 0 1 0 v -0.244 -0.0447 0.152 @@ -3634,17 +3634,17 @@ vn 0.0296 0 -1 v -0.237 -0.0502 0.147 vn 0.0296 0 -1 v -0.237 -0.0447 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 vn -0.0294 0 1 v -0.241 -0.0447 0.15 @@ -3718,17 +3718,17 @@ vn -0.998 0 -0.0581 v -0.247 -0.0502 0.149 vn -0.998 0 -0.0581 v -0.247 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 vn 0.998 0 0.057 v -0.246 -0.0447 0.152 @@ -3940,29 +3940,29 @@ vn -0.0144 0 -1 v -0.301 -0.0583 0.141 vn -0.0144 0 -1 v -0.301 -0.0529 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000501 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000501 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000501 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000501 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000501 0 1 v -0.302 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000501 0 1 v -0.302 -0.0529 0.145 vn 1 0 -0.0275 v -0.302 -0.0529 0.147 @@ -4701,17 +4701,17 @@ vn 0 1 0 v -0.248 -0.0136 -0.0633 vn 0 1 0 v -0.259 -0.0136 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 vn 0.0152 0 -1 v -0.248 -0.0136 -0.0655 @@ -6533,17 +6533,17 @@ vn 0 1 0 v -0.212 -0.048 0.153 vn 0 1 0 v -0.216 -0.048 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.048 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.216 -0.0534 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.216 -0.0534 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.048 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.0534 0.15 -vn 0.0514 0 -0.999 +vn 0.0513 0 -0.999 v -0.211 -0.048 0.15 vn 0.999 0 0.0516 v -0.212 -0.048 0.153 @@ -7241,17 +7241,17 @@ vn 0.0724 0 -0.997 v -0.153 -0.0583 0.144 vn 0.0724 0 -0.997 v -0.153 -0.0529 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 vn -0.088 0 0.996 v -0.172 -0.0529 0.146 @@ -7589,17 +7589,17 @@ vn -0.00872 0 -1 v -0.141 -0.0583 0.152 vn -0.00872 0 -1 v -0.141 -0.0529 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 vn 0.0106 0 1 v -0.144 -0.0529 0.155 @@ -7661,17 +7661,17 @@ vn 1 0 -0.0206 v -0.188 -0.0551 0.16 vn 1 0 -0.0206 v -0.188 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 vn -1 0 0.0206 v -0.191 -0.0496 0.155 @@ -11334,17 +11334,17 @@ vn 0 1 0 v -0.0224 -0.048 0.197 vn 0 1 0 v -0.0297 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 vn 1 0 0.00114 v -0.026 -0.048 0.197 @@ -11406,29 +11406,29 @@ vn -1 0 -0.000812 v -0.0285 -0.0534 0.196 vn -1 0 -0.000812 v -0.0285 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000608 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000608 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000608 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000608 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000608 0 1 v -0.0297 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000608 0 1 v -0.0297 -0.048 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.048 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.0534 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.0534 0.196 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.048 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0297 -0.0534 0.193 -vn -1 0 -0.00092 +vn -1 0 -0.000919 v -0.0338 -0.0496 0.197 vn 0 1 0 v -0.0374 -0.0496 0.197 @@ -11466,17 +11466,17 @@ vn 0 1 0 v -0.0297 -0.0496 0.193 vn 0 1 0 v -0.0297 -0.0496 0.193 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0496 0.196 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0551 0.193 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0551 0.193 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0496 0.196 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0551 0.196 -vn 1 0 0.00092 +vn 1 0 0.000919 v -0.0297 -0.0496 0.196 vn -0.00321 0 1 v -0.0313 -0.0496 0.196 @@ -11514,29 +11514,29 @@ vn -0.00126 0 1 v -0.0374 -0.0551 0.201 vn -0.00126 0 1 v -0.0374 -0.0496 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0496 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0551 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0551 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0496 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0551 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 @@ -11550,17 +11550,17 @@ vn -1 0 -0.00115 v -0.0338 -0.0551 0.193 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0524 -0.0529 0.201 vn 0 1 0 v -0.0524 -0.0529 0.207 @@ -11586,17 +11586,17 @@ vn 0 1 0 v -0.044 -0.0529 0.204 vn 0 1 0 v -0.0524 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 vn 1 0 0.0011 v -0.0485 -0.0529 0.204 @@ -11634,17 +11634,17 @@ vn 1 0 0.00162 v -0.044 -0.0583 0.207 vn 1 0 0.00162 v -0.044 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 vn -1 0 -0.00134 v -0.0524 -0.0529 0.201 @@ -11844,17 +11844,17 @@ vn 0 1 0 v -0.0463 -0.0513 0.186 vn 0 1 0 v -0.0513 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 vn -0.0182 0 -1 v -0.0464 -0.0513 0.182 @@ -12378,17 +12378,17 @@ vn 0 1 0 v -0.0423 -0.048 0.17 vn 0 1 0 v -0.0453 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 @@ -12402,17 +12402,17 @@ vn 1 0 -0.0167 v -0.0423 -0.0534 0.17 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 vn -1 0 0.0167 v -0.0453 -0.048 0.165 @@ -14124,17 +14124,17 @@ vn 0 1 0 v 0.00615 -0.0398 0.16 vn 0 1 0 v 0.000675 -0.0398 0.164 -vn -1 0 -0.00085 +vn -1 0 -0.000851 v 0.00068 -0.0398 0.158 -vn -1 0 -0.00085 +vn -1 0 -0.000851 v 0.000675 -0.0453 0.164 -vn -1 0 -0.00085 +vn -1 0 -0.000851 v 0.000675 -0.0453 0.164 -vn -1 0 -0.00085 +vn -1 0 -0.000851 v 0.00068 -0.0398 0.158 -vn -1 0 -0.00085 +vn -1 0 -0.000851 v 0.00068 -0.0453 0.158 -vn -1 0 -0.00085 +vn -1 0 -0.000851 v 0.00068 -0.0398 0.158 vn 0.00159 0 -1 v 0.00247 -0.0398 0.158 @@ -14160,17 +14160,17 @@ vn -1 0 0.00135 v 0.00247 -0.0453 0.157 vn -1 0 0.00135 v 0.00247 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00247 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00247 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 vn 1 0 0.00134 v 0.00626 -0.0398 0.158 @@ -14184,29 +14184,29 @@ vn 1 0 0.00134 v 0.00626 -0.0453 0.158 vn 1 0 0.00134 v 0.00626 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.00626 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.00626 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0398 0.16 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0453 0.158 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0453 0.158 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0398 0.16 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0453 0.16 -vn 1 0 0.000833 +vn 1 0 0.000831 v 0.0055 -0.0398 0.16 vn -0.00351 0 -1 v 0.00615 -0.0398 0.16 @@ -14298,17 +14298,17 @@ vn -1 0 -0.00363 v -0.000303 -0.0469 0.164 vn -1 0 -0.00363 v -0.000303 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 vn 0.00276 0 -1 v 0.0041 -0.0414 0.164 @@ -17901,17 +17901,17 @@ vn -1 0 -0.00929 v 0.0624 -0.0583 0.154 vn -1 0 -0.00929 v 0.0624 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 vn -1 0 -0.00721 v 0.0615 -0.0529 0.153 @@ -21333,17 +21333,17 @@ vn 1 0 -0.0108 v 0.0707 -0.0583 0.152 vn 1 0 -0.0108 v 0.0707 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 vn -1 0 -0.0151 v 0.0673 -0.0529 0.151 @@ -21789,17 +21789,17 @@ vn 0.999 0 0.0411 v 0.0996 -0.0551 0.144 vn 0.999 0 0.0411 v 0.0996 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0996 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0996 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 vn -0.999 0 -0.0426 v 0.0977 -0.0496 0.142 @@ -24291,17 +24291,17 @@ vn 1 0 0.00875 v 0.0708 -0.0583 0.16 vn 1 0 0.00875 v 0.0708 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0708 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0708 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 vn -1 0 -0.00885 v 0.0685 -0.0529 0.158 @@ -26380,17 +26380,17 @@ vn -0.951 0 0.31 v 0.107 -0.0518 0.0743 vn -0.951 0 0.31 v 0.107 -0.0463 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.107 -0.0518 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.107 -0.0518 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0518 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 vn -0.985 0 0.175 v 0.108 -0.0463 0.0724 @@ -30508,17 +30508,17 @@ vn -0.0107 0 -1 v 0.217 -0.0583 0.449 vn -0.0107 0 -1 v 0.217 -0.0529 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 vn -0.0197 0 1 v 0.213 -0.0529 0.458 @@ -30544,17 +30544,17 @@ vn -1 0 -0.00435 v 0.213 -0.0583 0.454 vn -1 0 -0.00435 v 0.213 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 vn -1 0 0.019 v 0.207 -0.0529 0.444 @@ -31810,41 +31810,41 @@ vn -1 0 0.00184 v 0.227 -0.06 0.444 vn -1 0 0.00184 v 0.227 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.227 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.227 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.0545 0.439 vn 1 0 -0.00192 v 0.232 -0.0545 0.443 @@ -31882,29 +31882,29 @@ vn 1 0 0.00107 v 0.231 -0.06 0.444 vn 1 0 0.00107 v 0.231 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 vn -0.0268 0 1 v 0.232 -0.0545 0.445 @@ -32785,17 +32785,17 @@ vn 0.538 0 0.843 v 0.258 -0.0485 0.36 vn 0.538 0 0.843 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.0152 0 1 v 0.258 -0.0431 0.36 vn -0.854 0 0.52 v 0.258 -0.0431 0.36 @@ -33537,17 +33537,17 @@ vn 0.877 0 0.481 v 0.194 -0.0354 0.121 vn 0.877 0 0.481 v 0.194 -0.03 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.0354 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 vn 0.877 0 0.481 v 0.194 -0.03 0.126 @@ -35235,17 +35235,17 @@ vn 0.494 0 0.87 v 0.174 -0.0403 0.159 vn 0.494 0 0.87 v 0.174 -0.0349 0.159 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.173 -0.0349 0.157 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.174 -0.0403 0.159 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.174 -0.0403 0.159 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.173 -0.0349 0.157 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.173 -0.0403 0.157 -vn -0.868 0 0.496 +vn -0.868 0 0.497 v 0.258 -0.0284 0.187 vn 0 1 0 v 0.256 -0.0284 0.187 @@ -37155,17 +37155,17 @@ vn 0.582 0 -0.813 v 0.146 -0.0485 0.17 vn 0.582 0 -0.813 v 0.146 -0.0431 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0485 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 vn -0.582 0 0.813 v 0.142 -0.0431 0.17 @@ -44555,17 +44555,17 @@ vn 0.0151 0 -1 v 0.282 -0.0142 0.0868 vn 0.0151 0 -1 v 0.282 -0.00872 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 vn 0.0151 0 -1 v 0.285 -0.00872 0.0879 @@ -45479,17 +45479,17 @@ vn 0 1 0 v 0.276 -0.00872 0.0908 vn 0 1 0 v 0.272 -0.00872 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.0142 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 vn 0.0461 0 -0.999 v 0.276 -0.00872 0.0908 @@ -46175,17 +46175,17 @@ vn 0.628 0 0.778 v 0.422 0.0382 0.0445 vn 0.628 0 0.778 v 0.422 0.0436 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0382 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 f -78//-78 -77//-77 -76//-76 f -75//-75 -74//-74 -73//-73 f -72//-72 -71//-71 -70//-70 diff --git a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-5.obj b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-5.obj index bf50be61533..088e511c2dd 100644 --- a/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-5.obj +++ b/tests/testdata/control_files/3d/expected_big_scene_export/expected_big_scene_export-5.obj @@ -4336,17 +4336,17 @@ vn 0.877 0 0.481 v 0.194 -0.0354 0.121 vn 0.877 0 0.481 v 0.194 -0.03 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.194 -0.0354 0.121 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.0354 0.122 -vn 0.478 0 -0.878 +vn 0.478 0 -0.879 v 0.196 -0.03 0.122 vn 0.877 0 0.481 v 0.194 -0.03 0.126 @@ -5590,17 +5590,17 @@ vn 0.582 0 -0.813 v 0.146 -0.0485 0.17 vn 0.582 0 -0.813 v 0.146 -0.0431 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.146 -0.0485 0.17 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0485 0.172 -vn 0.812 0 0.583 +vn 0.813 0 0.583 v 0.145 -0.0431 0.172 vn -0.582 0 0.813 v 0.142 -0.0431 0.17 @@ -7689,17 +7689,17 @@ vn -0.0107 0 -1 v 0.217 -0.0583 0.449 vn -0.0107 0 -1 v 0.217 -0.0529 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.449 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0583 0.459 -vn 1 0 0.00467 +vn 1 0 0.00468 v 0.217 -0.0529 0.459 vn -0.0197 0 1 v 0.213 -0.0529 0.458 @@ -7725,17 +7725,17 @@ vn -1 0 -0.00435 v 0.213 -0.0583 0.454 vn -1 0 -0.00435 v 0.213 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.213 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0583 0.454 -vn 0.0248 0 1 +vn 0.0247 0 1 v 0.208 -0.0529 0.454 vn -1 0 0.019 v 0.207 -0.0529 0.444 @@ -8991,41 +8991,41 @@ vn -1 0 0.00184 v 0.227 -0.06 0.444 vn -1 0 0.00184 v 0.227 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.227 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.227 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.06 0.444 -vn 0.00115 0 -1 +vn 0.00116 0 -1 v 0.228 -0.0545 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.444 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.06 0.439 -vn -1 0 0.00089 +vn -1 0 0.000891 v 0.228 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.228 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.0545 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.06 0.439 -vn -0.000733 0 -1 +vn -0.000736 0 -1 v 0.232 -0.0545 0.439 vn 1 0 -0.00192 v 0.232 -0.0545 0.443 @@ -9063,29 +9063,29 @@ vn 1 0 0.00107 v 0.231 -0.06 0.444 vn 1 0 0.00107 v 0.231 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.231 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.06 0.444 -vn -0.00205 0 -1 +vn -0.00206 0 -1 v 0.233 -0.0545 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.444 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.06 0.446 -vn 1 0 -0.00202 +vn 1 0 -0.00203 v 0.233 -0.0545 0.446 vn -0.0268 0 1 v 0.232 -0.0545 0.445 @@ -9644,17 +9644,17 @@ vn 0.538 0 0.843 v 0.258 -0.0485 0.36 vn 0.538 0 0.843 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0431 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0485 0.36 -vn -0.0131 0 1 +vn -0.015 0 1 v 0.258 -0.0431 0.36 vn -0.854 0 0.52 v 0.258 -0.0431 0.36 @@ -16681,17 +16681,17 @@ vn 0.0151 0 -1 v 0.282 -0.0142 0.0868 vn 0.0151 0 -1 v 0.282 -0.00872 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0868 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.0142 0.0879 -vn 1 0 0.015 +vn 1 0 0.0151 v 0.282 -0.00872 0.0879 vn 0.0151 0 -1 v 0.285 -0.00872 0.0879 @@ -17359,17 +17359,17 @@ vn 0 1 0 v 0.276 -0.00872 0.0908 vn 0 1 0 v 0.272 -0.00872 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.272 -0.0142 0.0923 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.0142 0.0906 -vn -0.999 0 -0.0451 +vn -0.999 0 -0.0452 v 0.273 -0.00872 0.0906 vn 0.0461 0 -0.999 v 0.276 -0.00872 0.0908 @@ -18529,17 +18529,17 @@ vn 0.628 0 0.778 v 0.422 0.0382 0.0445 vn 0.628 0 0.778 v 0.422 0.0436 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.422 0.0382 0.0445 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0436 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 v 0.42 0.0382 0.0417 -vn -0.776 0 0.631 +vn -0.776 0 0.63 f -48//-48 -47//-47 -46//-46 f -45//-45 -44//-44 -43//-43 f -42//-42 -41//-41 -40//-40 @@ -19765,17 +19765,17 @@ vn 0.474 0 -0.88 v -0.307 -0.0682 0.276 vn 0.474 0 -0.88 v -0.307 -0.0627 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.307 -0.0682 0.276 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0682 0.278 -vn 0.876 0 0.482 +vn 0.876 0 0.481 v -0.308 -0.0627 0.278 vn 0.883 0 0.47 v -0.309 -0.0627 0.28 @@ -20512,29 +20512,29 @@ vn -0.0144 0 -1 v -0.301 -0.0583 0.141 vn -0.0144 0 -1 v -0.301 -0.0529 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.141 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0583 0.145 -vn 1 0 -0.00659 +vn 1 0 -0.00658 v -0.301 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.301 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0529 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0583 0.145 -vn 0.00049 0 1 +vn 0.000499 0 1 v -0.302 -0.0529 0.145 vn 1 0 -0.0275 v -0.302 -0.0529 0.147 @@ -21243,17 +21243,17 @@ vn 0.879 0 0.476 v -0.291 -0.0714 0.29 vn 0.879 0 0.476 v -0.291 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.291 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.0714 0.29 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.292 -0.066 0.29 vn -0.474 0 0.88 v -0.294 -0.066 0.289 @@ -21471,17 +21471,17 @@ vn 0.471 0 -0.882 v -0.285 -0.0714 0.289 vn 0.471 0 -0.882 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.0714 0.289 -vn 0.878 0 0.478 +vn 0.879 0 0.478 v -0.285 -0.066 0.289 vn 0.475 0 -0.88 v -0.283 -0.066 0.291 @@ -21934,17 +21934,17 @@ vn -0.0308 0 1 v -0.237 -0.0502 0.15 vn -0.0308 0 1 v -0.237 -0.0447 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0447 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.15 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0447 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.237 -0.0502 0.147 -vn -1 0 -0.0302 +vn -1 0 -0.0301 v -0.244 -0.0447 0.147 vn 0 1 0 v -0.244 -0.0447 0.152 @@ -21982,17 +21982,17 @@ vn 0.0296 0 -1 v -0.237 -0.0502 0.147 vn 0.0296 0 -1 v -0.237 -0.0447 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.147 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0502 0.15 -vn 1 0 0.0302 +vn 1 0 0.0301 v -0.237 -0.0447 0.15 vn -0.0294 0 1 v -0.241 -0.0447 0.15 @@ -22066,17 +22066,17 @@ vn -0.998 0 -0.0581 v -0.247 -0.0502 0.149 vn -0.998 0 -0.0581 v -0.247 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.247 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0502 0.149 -vn 0.0575 0 -0.998 +vn 0.0576 0 -0.998 v -0.245 -0.0447 0.149 vn 0.998 0 0.057 v -0.246 -0.0447 0.152 @@ -23218,17 +23218,17 @@ vn 0 1 0 v -0.248 -0.0136 -0.0633 vn 0 1 0 v -0.259 -0.0136 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0634 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0191 -0.0657 -vn -1 0 -0.0153 +vn -1 0 -0.0152 v -0.259 -0.0136 -0.0657 vn 0.0152 0 -1 v -0.248 -0.0136 -0.0655 @@ -24414,17 +24414,17 @@ vn 1 0 -0.0206 v -0.188 -0.0551 0.16 vn 1 0 -0.0206 v -0.188 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.188 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0551 0.16 -vn 0.0198 0 1 +vn 0.0199 0 1 v -0.191 -0.0496 0.16 vn -1 0 0.0206 v -0.191 -0.0496 0.155 @@ -25758,17 +25758,17 @@ vn 0.0724 0 -0.997 v -0.153 -0.0583 0.144 vn 0.0724 0 -0.997 v -0.153 -0.0529 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.144 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0583 0.147 -vn 0.996 0 0.0897 +vn 0.996 0 0.0896 v -0.153 -0.0529 0.147 vn -0.088 0 0.996 v -0.172 -0.0529 0.146 @@ -26046,17 +26046,17 @@ vn -0.00872 0 -1 v -0.141 -0.0583 0.152 vn -0.00872 0 -1 v -0.141 -0.0529 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.152 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0583 0.155 -vn 1 0 -0.00923 +vn 1 0 -0.00924 v -0.141 -0.0529 0.155 vn 0.0106 0 1 v -0.144 -0.0529 0.155 @@ -27803,17 +27803,17 @@ vn 0 1 0 v -0.044 -0.0529 0.204 vn 0 1 0 v -0.0524 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0524 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0583 0.201 -vn 0.000966 0 -1 +vn 0.000964 0 -1 v -0.0485 -0.0529 0.201 vn 1 0 0.0011 v -0.0485 -0.0529 0.204 @@ -27851,17 +27851,17 @@ vn 1 0 0.00162 v -0.044 -0.0583 0.207 vn 1 0 0.00162 v -0.044 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.044 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0583 0.207 -vn -0.000634 0 1 +vn -0.000636 0 1 v -0.0524 -0.0529 0.207 vn -1 0 -0.00134 v -0.0524 -0.0529 0.201 @@ -28061,17 +28061,17 @@ vn 0 1 0 v -0.0463 -0.0513 0.186 vn 0 1 0 v -0.0513 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0513 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0567 0.182 -vn -0.0153 0 -1 +vn -0.0152 0 -1 v -0.0499 -0.0513 0.182 vn -0.0182 0 -1 v -0.0464 -0.0513 0.182 @@ -29984,17 +29984,17 @@ vn 0 1 0 v -0.0224 -0.048 0.197 vn 0 1 0 v -0.0297 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.0297 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.0534 0.193 -vn 0.000756 0 -1 +vn 0.000755 0 -1 v -0.026 -0.048 0.193 vn 1 0 0.00114 v -0.026 -0.048 0.197 @@ -30056,17 +30056,17 @@ vn -1 0 -0.000812 v -0.0285 -0.0534 0.196 vn -1 0 -0.000812 v -0.0285 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0285 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.048 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.0534 0.196 -vn -0.000612 0 1 +vn -0.000609 0 1 v -0.0297 -0.048 0.196 vn -1 0 -0.00092 v -0.0297 -0.048 0.193 @@ -30164,29 +30164,29 @@ vn -0.00126 0 1 v -0.0374 -0.0551 0.201 vn -0.00126 0 1 v -0.0374 -0.0496 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0496 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0551 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0551 0.201 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0496 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0551 0.197 -vn -1 0 -0.000968 +vn -1 0 -0.000969 v -0.0374 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0374 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0551 0.197 -vn -0.000102 0 -1 +vn -0.000105 0 -1 v -0.0338 -0.0496 0.197 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 @@ -30200,17 +30200,17 @@ vn -1 0 -0.00115 v -0.0338 -0.0551 0.193 vn -1 0 -0.00115 v -0.0338 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0338 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0496 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0297 -0.0551 0.193 -vn 0.0017 0 -1 +vn 0.00169 0 -1 v -0.0319 -0.048 0.184 vn 0 1 0 v -0.0331 -0.048 0.184 @@ -31082,17 +31082,17 @@ vn 0 1 0 v -0.0423 -0.048 0.17 vn 0 1 0 v -0.0453 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0453 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.0534 0.165 -vn 0.00275 0 -1 +vn 0.00274 0 -1 v -0.0424 -0.048 0.165 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 @@ -31106,17 +31106,17 @@ vn 1 0 -0.0167 v -0.0423 -0.0534 0.17 vn 1 0 -0.0167 v -0.0423 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0423 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.0534 0.17 -vn -0.00275 0 1 +vn -0.00274 0 1 v -0.0452 -0.048 0.17 vn -1 0 0.0167 v -0.0453 -0.048 0.165 @@ -31868,53 +31868,53 @@ vn 0.00159 0 -1 v 0.00247 -0.0453 0.158 vn 0.00159 0 -1 v 0.00247 -0.0398 0.158 -vn -1 0 0.00135 +vn -1 0 0.00136 v 0.00247 -0.0398 0.157 -vn -1 0 0.00135 +vn -1 0 0.00136 v 0.00247 -0.0453 0.158 -vn -1 0 0.00135 +vn -1 0 0.00136 v 0.00247 -0.0453 0.158 -vn -1 0 0.00135 +vn -1 0 0.00136 v 0.00247 -0.0398 0.157 -vn -1 0 0.00135 +vn -1 0 0.00136 v 0.00247 -0.0453 0.157 -vn -1 0 0.00135 +vn -1 0 0.00136 v 0.00247 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00247 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00247 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0453 0.157 -vn 0.000759 0 -1 +vn 0.000758 0 -1 v 0.00626 -0.0398 0.157 -vn 1 0 0.00134 +vn 1 0 0.00133 v 0.00626 -0.0398 0.158 -vn 1 0 0.00134 +vn 1 0 0.00133 v 0.00626 -0.0453 0.157 -vn 1 0 0.00134 +vn 1 0 0.00133 v 0.00626 -0.0453 0.157 -vn 1 0 0.00134 +vn 1 0 0.00133 v 0.00626 -0.0398 0.158 -vn 1 0 0.00134 +vn 1 0 0.00133 v 0.00626 -0.0453 0.158 -vn 1 0 0.00134 +vn 1 0 0.00133 v 0.00626 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.00626 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.00626 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0453 0.158 -vn -0.00709 0 1 +vn -0.0071 0 1 v 0.0055 -0.0398 0.158 vn 1 0 0.000833 v 0.0055 -0.0398 0.16 @@ -32018,17 +32018,17 @@ vn -1 0 -0.00363 v -0.000303 -0.0469 0.164 vn -1 0 -0.00363 v -0.000303 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v -0.000303 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0469 0.164 -vn 0.00277 0 -1 +vn 0.00276 0 -1 v 0.000675 -0.0414 0.164 vn 0.00276 0 -1 v 0.0041 -0.0414 0.164 @@ -35910,17 +35910,17 @@ vn -1 0 -0.00929 v 0.0624 -0.0583 0.154 vn -1 0 -0.00929 v 0.0624 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0624 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0583 0.154 -vn -0.0088 0 1 +vn -0.00879 0 1 v 0.0615 -0.0529 0.154 vn -1 0 -0.00721 v 0.0615 -0.0529 0.153 @@ -36696,17 +36696,17 @@ vn 1 0 -0.0108 v 0.0707 -0.0583 0.152 vn 1 0 -0.0108 v 0.0707 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0707 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0583 0.152 -vn 0.00968 0 1 +vn 0.00969 0 1 v 0.0673 -0.0529 0.152 vn -1 0 -0.0151 v 0.0673 -0.0529 0.151 @@ -37728,17 +37728,17 @@ vn 1 0 0.00875 v 0.0708 -0.0583 0.16 vn 1 0 0.00875 v 0.0708 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0708 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0708 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0583 0.16 -vn -0.0098 0 1 +vn -0.00981 0 1 v 0.0685 -0.0529 0.16 vn -1 0 -0.00885 v 0.0685 -0.0529 0.158 @@ -43613,17 +43613,17 @@ vn 0.999 0 0.0411 v 0.0996 -0.0551 0.144 vn 0.999 0 0.0411 v 0.0996 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0996 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0996 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0551 0.144 -vn -0.0391 0 0.999 +vn -0.0392 0 0.999 v 0.0976 -0.0496 0.144 vn -0.999 0 -0.0426 v 0.0977 -0.0496 0.142 @@ -44551,17 +44551,17 @@ vn -0.951 0 0.31 v 0.107 -0.0518 0.0743 vn -0.951 0 0.31 v 0.107 -0.0463 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.107 -0.0518 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.107 -0.0518 0.0743 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0518 0.0742 -vn -0.0937 0 -0.996 +vn -0.0938 0 -0.996 v 0.108 -0.0463 0.0742 vn -0.985 0 0.175 v 0.108 -0.0463 0.0724 diff --git a/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/default/expected_polygon3d_extrusion_textured_phong.png b/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/default/expected_polygon3d_extrusion_textured_phong.png index 8fe83dc9a89..4bad175eb7d 100644 Binary files a/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/default/expected_polygon3d_extrusion_textured_phong.png and b/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/default/expected_polygon3d_extrusion_textured_phong.png differ diff --git a/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/qt6/expected_polygon3d_extrusion_textured_phong.png b/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/qt6/expected_polygon3d_extrusion_textured_phong.png old mode 100644 new mode 100755 index a13ddfc72a1..26db02e4a5d Binary files a/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/qt6/expected_polygon3d_extrusion_textured_phong.png and b/tests/testdata/control_images/3d/expected_polygon3d_extrusion_textured_phong/qt6/expected_polygon3d_extrusion_textured_phong.png differ diff --git a/tests/testdata/control_images/expected_geometrygenerator_field_geometry/expected_geometrygenerator_field_geometry.png b/tests/testdata/control_images/expected_geometrygenerator_field_geometry/expected_geometrygenerator_field_geometry.png new file mode 100644 index 00000000000..d6990d4314d Binary files /dev/null and b/tests/testdata/control_images/expected_geometrygenerator_field_geometry/expected_geometrygenerator_field_geometry.png differ diff --git a/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode.png b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode.png new file mode 100644 index 00000000000..c8880c5dbdd Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode.png differ diff --git a/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode_mask.png b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode_mask.png new file mode 100644 index 00000000000..2de2dc93c92 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/text_with_buffer_blend_mode/text_with_buffer_blend_mode_mask.png differ diff --git a/tests/testdata/ogr/gtfs_extract.zip b/tests/testdata/ogr/gtfs_extract.zip new file mode 100644 index 00000000000..69538aa3916 Binary files /dev/null and b/tests/testdata/ogr/gtfs_extract.zip differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/0-0.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/0-0.copc.laz new file mode 100644 index 00000000000..187f03a3c9c Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/0-0.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/0-1.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/0-1.copc.laz new file mode 100644 index 00000000000..c9790a104af Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/0-1.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/1-0.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/1-0.copc.laz new file mode 100644 index 00000000000..e42d651409c Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/1-0.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/1-1.copc.laz b/tests/testdata/point_clouds/virtual/sunshine-coast/1-1.copc.laz new file mode 100644 index 00000000000..e08a4a146bc Binary files /dev/null and b/tests/testdata/point_clouds/virtual/sunshine-coast/1-1.copc.laz differ diff --git a/tests/testdata/point_clouds/virtual/sunshine-coast/combined.vpc b/tests/testdata/point_clouds/virtual/sunshine-coast/combined.vpc new file mode 100644 index 00000000000..b353cfea7b0 --- /dev/null +++ b/tests/testdata/point_clouds/virtual/sunshine-coast/combined.vpc @@ -0,0 +1,853 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "0-0.copc", + "geometry": { + "coordinates": [ + [ + [ + 498062.0, + 7050992.85, + 74.68 + ], + [ + 498062.0, + 7050995.82, + 74.68 + ], + [ + 498064.98, + 7050995.82, + 75.16 + ], + [ + 498064.98, + 7050992.85, + 75.16 + ], + [ + 498062.0, + 7050992.85, + 74.68 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498062.0, + 7050992.85, + 74.68, + 498064.98, + 7050995.82, + 75.16 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 91, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498062.0, + 7050992.85, + 74.68, + 498064.98, + 7050995.82, + 75.16 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498062.0, + 7050992.85, + 74.68 + ], + [ + 498062.0, + 7050995.82, + 74.68 + ], + [ + 498064.98, + 7050995.82, + 75.16 + ], + [ + 498064.98, + 7050992.85, + 75.16 + ], + [ + 498062.0, + 7050992.85, + 74.68 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./0-0.copc.laz", + "roles": [ + "data" + ] + } + } + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "0-1.copc", + "geometry": { + "coordinates": [ + [ + [ + 498062.04, + 7050995.84, + 74.64 + ], + [ + 498062.04, + 7050997.03, + 74.64 + ], + [ + 498064.91, + 7050997.03, + 79.0 + ], + [ + 498064.91, + 7050995.84, + 79.0 + ], + [ + 498062.04, + 7050995.84, + 74.64 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498062.04, + 7050995.84, + 74.64, + 498064.91, + 7050997.03, + 79.0 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 47, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498062.04, + 7050995.84, + 74.64, + 498064.91, + 7050997.03, + 79.0 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498062.04, + 7050995.84, + 74.64 + ], + [ + 498062.04, + 7050997.03, + 74.64 + ], + [ + 498064.91, + 7050997.03, + 79.0 + ], + [ + 498064.91, + 7050995.84, + 79.0 + ], + [ + 498062.04, + 7050995.84, + 74.64 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./0-1.copc.laz", + "roles": [ + "data" + ] + } + } + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "1-0.copc", + "geometry": { + "coordinates": [ + [ + [ + 498065.0, + 7050992.84, + 74.46 + ], + [ + 498065.0, + 7050995.83, + 74.46 + ], + [ + 498067.39, + 7050995.83, + 74.91 + ], + [ + 498067.39, + 7050992.84, + 74.91 + ], + [ + 498065.0, + 7050992.84, + 74.46 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498065.0, + 7050992.84, + 74.46, + 498067.39, + 7050995.83, + 74.91 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 77, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498065.0, + 7050992.84, + 74.46, + 498067.39, + 7050995.83, + 74.91 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498065.0, + 7050992.84, + 74.46 + ], + [ + 498065.0, + 7050995.83, + 74.46 + ], + [ + 498067.39, + 7050995.83, + 74.91 + ], + [ + 498067.39, + 7050992.84, + 74.91 + ], + [ + 498065.0, + 7050992.84, + 74.46 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./1-0.copc.laz", + "roles": [ + "data" + ] + } + } + }, + { + "type": "Feature", + "stac_version": "1.0.0", + "stac_extensions": [ + "https://stac-extensions.github.io/pointcloud/v1.0.0/schema.json", + "https://stac-extensions.github.io/projection/v1.1.0/schema.json" + ], + "id": "1-1.copc", + "geometry": { + "coordinates": [ + [ + [ + 498065.01, + 7050995.9, + 74.34 + ], + [ + 498065.01, + 7050997.04, + 74.34 + ], + [ + 498067.32, + 7050997.04, + 80.02 + ], + [ + 498067.32, + 7050995.9, + 80.02 + ], + [ + 498065.01, + 7050995.9, + 74.34 + ] + ] + ], + "type": "Polygon" + }, + "bbox": [ + 498065.01, + 7050995.9, + 74.34, + 498067.32, + 7050997.04, + 80.02 + ], + "properties": { + "datetime": "2024-10-18T00:00:00Z", + "pc:count": 38, + "pc:encoding": "?", + "pc:schemas": [ + { + "name": "X", + "size": 8, + "type": "floating" + }, + { + "name": "Y", + "size": 8, + "type": "floating" + }, + { + "name": "Z", + "size": 8, + "type": "floating" + }, + { + "name": "Intensity", + "size": 2, + "type": "unsigned" + }, + { + "name": "ReturnNumber", + "size": 1, + "type": "unsigned" + }, + { + "name": "NumberOfReturns", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanDirectionFlag", + "size": 1, + "type": "unsigned" + }, + { + "name": "EdgeOfFlightLine", + "size": 1, + "type": "unsigned" + }, + { + "name": "Classification", + "size": 1, + "type": "unsigned" + }, + { + "name": "Synthetic", + "size": 1, + "type": "unsigned" + }, + { + "name": "KeyPoint", + "size": 1, + "type": "unsigned" + }, + { + "name": "Withheld", + "size": 1, + "type": "unsigned" + }, + { + "name": "Overlap", + "size": 1, + "type": "unsigned" + }, + { + "name": "ScanAngleRank", + "size": 4, + "type": "floating" + }, + { + "name": "UserData", + "size": 1, + "type": "unsigned" + }, + { + "name": "PointSourceId", + "size": 2, + "type": "unsigned" + }, + { + "name": "GpsTime", + "size": 8, + "type": "floating" + }, + { + "name": "ScanChannel", + "size": 1, + "type": "unsigned" + }, + { + "name": "Red", + "size": 2, + "type": "unsigned" + }, + { + "name": "Green", + "size": 2, + "type": "unsigned" + }, + { + "name": "Blue", + "size": 2, + "type": "unsigned" + } + ], + "pc:type": "lidar", + "proj:bbox": [ + 498065.01, + 7050995.9, + 74.34, + 498067.32, + 7050997.04, + 80.02 + ], + "proj:geometry": { + "coordinates": [ + [ + [ + 498065.01, + 7050995.9, + 74.34 + ], + [ + 498065.01, + 7050997.04, + 74.34 + ], + [ + 498067.32, + 7050997.04, + 80.02 + ], + [ + 498067.32, + 7050995.9, + 80.02 + ], + [ + 498065.01, + 7050995.9, + 74.34 + ] + ] + ], + "type": "Polygon" + }, + "proj:wkt2": "" + }, + "links": [], + "assets": { + "data": { + "href": "./1-1.copc.laz", + "roles": [ + "data" + ] + } + } + } + ] +} diff --git a/tests/testdata/stac/collectioncollection-sample-full.json b/tests/testdata/stac/collectioncollection-sample-full.json new file mode 100644 index 00000000000..e26616b726d --- /dev/null +++ b/tests/testdata/stac/collectioncollection-sample-full.json @@ -0,0 +1,85 @@ +{ + "collections": [ + { + "id": "simple-collection", + "type": "Collection", + "stac_extensions": [], + "stac_version": "1.0.0", + "description": "A simple collection demonstrating core collection fields in an API", + "title": "Simple Example Collection", + "providers": [ + { + "name": "Remote Data, Inc", + "description": "Producers of awesome spatiotemporal assets", + "roles": [ + "producer", + "processor" + ], + "url": "http://remotedata.io" + } + ], + "extent": { + "spatial": { + "bbox": [ + [ + 172.91173669923782, + 1.3438851951615003, + 172.95469614953714, + 1.3690476620161975 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-12-11T22:38:32.125Z", + "2020-12-14T18:02:31.437Z" + ] + ] + } + }, + "license": "CC-BY-4.0", + "links": [ + { + "rel": "root", + "href": "http://stac.example.com/", + "type": "application/json" + }, + { + "rel": "items", + "href": "http://stac.example.com/collections/simple-collection/items", + "type": "application/geo+json" + }, + { + "rel": "self", + "href": "http://stac.example.com/collections/simple-collection", + "type": "application/json" + } + ] + } + ], + "links": [ + { + "rel": "self", + "href": "http://stac.example.com/collections?page=2", + "type": "application/json" + }, + { + "rel": "root", + "href": "http://stac.example.com/", + "type": "application/json" + }, + { + "rel": "next", + "href": "http://stac.example.com/collections?page=3", + "type": "application/json" + }, + { + "rel": "prev", + "href": "http://stac.example.com/collections?page=1", + "type": "application/json" + } + ], + "numberMatched": 11, + "numberReturned": 1 +} diff --git a/tests/testdata/stac/core-item.json b/tests/testdata/stac/core-item.json index d07cc8df69b..766596225d7 100644 --- a/tests/testdata/stac/core-item.json +++ b/tests/testdata/stac/core-item.json @@ -81,7 +81,7 @@ ], "assets": { "analytic": { - "href": "https://storage.googleapis.com/open-cogs/stac-examples/20201211_223832_CS2_analytic.tif", + "href": "./20201211_223832_CS2_analytic.tif", "type": "image/tiff; application=geotiff; profile=cloud-optimized", "title": "4-Band Analytic", "roles": [ diff --git a/tests/testdata/stac/itemcollection-sample-full.json b/tests/testdata/stac/itemcollection-sample-full.json new file mode 100644 index 00000000000..e2fe86b5d0c --- /dev/null +++ b/tests/testdata/stac/itemcollection-sample-full.json @@ -0,0 +1,101 @@ +{ + "type": "FeatureCollection", + "numberMatched": 10, + "numberReturned": 1, + "features": [ + { + "stac_version": "1.0.0", + "stac_extensions": [], + "type": "Feature", + "id": "cs3-20160503_132131_05", + "bbox": [ + -122.59750209, + 37.48803556, + -122.2880486, + 37.613537207 + ], + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -122.308150179, + 37.488035566 + ], + [ + -122.597502109, + 37.538869539 + ], + [ + -122.576687533, + 37.613537207 + ], + [ + -122.288048600, + 37.562818007 + ], + [ + -122.308150179, + 37.488035566 + ] + ] + ] + }, + "properties": { + "datetime": "2016-05-03T13:22:30.040Z", + "title": "A CS3 item", + "license": "PDDL-1.0", + "providers": [ + { + "name": "CoolSat", + "roles": [ + "producer", + "licensor" + ], + "url": "https://stac-api.example.com" + } + ] + }, + "collection": "cs3", + "links": [ + { + "rel": "self", + "type": "application/json", + "href": "https://stac-api.example.com/collections/cs3/items/CS3-20160503_132131_05" + }, + { + "rel": "root", + "type": "application/json", + "href": "https://stac-api.example.com/" + }, + { + "rel": "collection", + "type": "application/json", + "href": "https://stac-api.example.com/collections/cs3" + } + ], + "assets": { + "analytic": { + "href": "https://stac-api.example.com/catalog/cs3-20160503_132130_04/analytic.tif", + "type": "image/tiff; application=geotiff; profile=cloud-optimized", + "title": "4-Band Analytic" + }, + "thumbnail": { + "href": "https://stac-api.example.com/catalog/cs3-20160503_132130_04/thumbnail.png", + "type": "image/png", + "title": "Thumbnail", + "roles": [ + "thumbnail" + ] + } + } + } + ], + "links": [ + { + "rel": "root", + "href": "http://stac.example.com/", + "type": "application/json" + } + ] +} \ No newline at end of file diff --git a/vcpkg/vcpkg.json b/vcpkg/vcpkg.json index b2716078a9c..4d236a26ccc 100644 --- a/vcpkg/vcpkg.json +++ b/vcpkg/vcpkg.json @@ -2,14 +2,13 @@ "vcpkg-configuration": { "default-registry": { "kind": "git", - "baseline": "7adc2e4d49e8d0efc07a369079faa6bc3dbb90f3", - "reference": "7adc2e4d49e8d0efc07a369079faa6bc3dbb90f3", + "baseline": "41626fd77bf42f29e8f7e43dc1f2f05780588cde", "repository": "https://github.com/microsoft/vcpkg" }, "registries": [ { "kind": "git", - "baseline": "d4bc16c09fae1c10c74827ffe73b6c54b287021d", + "baseline": "02f0c7fc53be545969c4e400dec079af9c82240a", "repository": "https://github.com/open-vcpkg/python-registry", "packages": [ "python3", @@ -60,6 +59,8 @@ }, "libxml2", "libzip", + "meshoptimizer", + "nlohmann-json", "pdal", "proj", "protobuf",