Skip to content

[3d] add snapping support to the measure line tool#64662

Open
ptitjano wants to merge 10 commits intoqgis:masterfrom
ptitjano:3d-snapping-v3
Open

[3d] add snapping support to the measure line tool#64662
ptitjano wants to merge 10 commits intoqgis:masterfrom
ptitjano:3d-snapping-v3

Conversation

@ptitjano
Copy link
Copy Markdown
Collaborator

Description

This PR adds snapping support to the measure line tool for vector layers. Contrary to #64329, this does not contain any vector highlight capability.

Snapping ToolBar

A Snapping toolbar is added to the 3D window. It allows to :

  • activate or deactivate the snapping
  • choose the snapping options (multiple options can be chosen at the same time)
  • change the snapping tolerance

The following snapping options are handled:

  • Snap on vertex
  • Snap on mid edge
  • Snap on face center
  • Snap on edge

Snapping Manager (Qgs3DSnappingManager)

Qgs3DSnappingManager is introduced to handle the 3D snapping and its options.

If no snapping is found or if the snapping is disabled, the closest point is simply returned.

If a snapping is found:

  • the nearest snapping point is highlighted with a marker (QgsRubberband3D). The shape of the marker depends on the snap type. They are similar to the 2D snapping tool. The marker is displayed by the rubberband pass of the framegraph to ensure it is always visible on top
  • the snapped point is returned

With this change the snapping manager has two purposes:

  • it is a ray tracing wrapper around the screen to world coordinates conversion
  • it highlights the current snapped point

The snapping manager is created by Qgs3DMapCanvasWidget. It can then be passed to the different tools which need it. The snapping options are retrieved by the values sets through the snapping toolbar

Ray tracing changes

QgsRayContext and QgsRayCastHit are extended to retrieve the vertex faces on hits. It allows to get the closest face, segments and points from the mouse cursor. It is then consumed by Qgs3DSnappingManager

Measure line tools changes

QgsMeasureLineTool is adapted to consume the results of Qgs3DSnappingManager and manage it:

  • When the tool is activated, the snapping manager is started
  • When the tool is deactivated, the snapping manager is stopped
  • When a new point is requested (on click or mouse movement), the snapping manager is called instead of directly calling the ray tracing tool.

Other changes

QgsRubberband3D is extended to handle more shapes. As a result, the marker shaped is stored as Qgis::MarkerShape instead of the custom QgsRubberBand3D::MarkerType

Demo

Kooha-2026-01-22-19-55-05.mp4

Funded by Stadt Frankfurt am Main

@ptitjano ptitjano changed the title [3d] add snapping support to the [3d] add snapping support to the measure line tool Jan 22, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 22, 2026

🪟 Windows Qt6 builds

Download Windows Qt6 builds of this PR for testing.
(Built from commit 5d70c8a)

🍎 MacOS Qt6 builds

Download MacOS Qt6 builds of this PR for testing.
This installer is not signed, control+click > open the app to avoid the warning
(Built from commit 5d70c8a)

@ptitjano ptitjano added Feature 3D Relates to QGIS' 3D engine or rendering labels Jan 23, 2026
@github-actions github-actions bot added this to the 4.0.0 milestone Jan 23, 2026
@uclaros
Copy link
Copy Markdown
Contributor

uclaros commented Jan 23, 2026

@ptitjano Thank you for dissociating the highlight and snapping code, the PR is now much easier to browse and the structure is clearer.

I have been looking into the method used for collecting snap candidates, which is a critical part of the design. Based on the code, the logic appears to be:

  1. Cast a ray on mouse move using the existing Qgs3DMapCanvas::castRay().
  2. Iterate the hit results and and get the mesh triangle coordinates from the QgsTessellatedPolygonGeometry by extending triangleIndexToFeatureId() to return the coordinates of the triangle vertices.
  3. Check for matches between the hit location and and the triangle face vertices according to the defined snapping mode using the introduced Qgs3DSnappingManager.

While this works for basic vertex snapping, relying on the existing ray casting introduces several architectural limitations that might prevent us from achieving full feature parity with 2D snapping:

  • Tessellation Artifacts: Snapping is performed on the render mesh (triangles) rather than the feature geometry. This causes false positives for segment and center snapping. Users expect to snap to the logical geometry (e.g., a square roof), not the implementation detail (two OpenGL triangles).
    Peek 2026-01-23 15-57

  • Point Layers: Currently, it is not possible to snap to point features. Implementing ray casting for points and retrieving specific coordinates would require significant additional logic.

  • Performance (Point Clouds): Ray casting against Point Cloud layers is computationally expensive (requiring proximity check on all points of intersecting nodes). Doing this on every mouseMove event is likely to cause performance issues.

  • Missed Candidates: Candidates are only identified if the mouse explicitly hovers the feature (a ray hit). If a cursor is within tolerance but pointing slightly outside the mesh (e.g., just off the edge of a roof), no candidate is found, which contradicts standard snapping behavior.
    Peek 2026-01-23 15-32

  • Units: This approach limits tolerance to map units. For 3D UX, pixel-based tolerance is usually preferred.

I have discussed these challenges with @wonder-sk. We believe a more robust approach would be to move away from ray casting and implement a "picking texture" workflow.

This would involve a separate render target (enabled only during snapping) to encode snapping data, similar to a depth buffer. Each pixel in the texture would store a key to recover the Layer ID, Feature ID, and Snapping Type for that location.
Pixel tolerance can be embedded directly (e.g., rendering a vertex as a circle with a radius of pixelTolerance).
Querying this texture would be extremely fast regardless of layer type (Vectors, Tiled Scenes, Point Clouds) and solves the "hover" issue mentioned above.

On a minor note, it would be beneficial to dissociate the snapping functionality from the Measurement Tool and integrate it into a parent class. This would allow the new Python-exposed 3D map tools to utilize snapping as well.

* In case such triangle index does not match any feature, FID_NULL is returned.
*/
QgsFeatureId triangleIndexToFeatureId( uint triangleIndex ) const;
QgsFeatureId triangleIndexToFeatureId( uint triangleIndex, QVector3D ( *facePoints )[3] = nullptr ) const;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a comment to explain the optional returned facePoints vector could be added.

Comment on lines +5 to +6
Copyright : (C) 2025 by Benoit De Mezzo
Email : benoit dot de dot mezzo at oslandia dot com
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Copyright : (C) 2025 by Benoit De Mezzo
Email : benoit dot de dot mezzo at oslandia dot com
Copyright : (C) 2025 by Oslandia
Email : benoit dot de dot mezzo at oslandia dot com, jean dot felder at oslandia dot com

Comment on lines +5 to +6
Copyright : (C) 2025 by Benoit De Mezzo
Email : benoit dot de dot mezzo at oslandia dot com
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Copyright : (C) 2025 by Benoit De Mezzo
Email : benoit dot de dot mezzo at oslandia dot com
Copyright : (C) 2025 by Oslandia
Email : benoit dot de dot mezzo at oslandia dot com, jean dot felder at oslandia dot com

Comment on lines +5 to +6
copyright : (C) 2025 Oslandia
email : benoit dot de dot mezzo at oslandia dot com
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
copyright : (C) 2025 Oslandia
email : benoit dot de dot mezzo at oslandia dot com
Copyright : (C) 2025 by Oslandia
Email : benoit dot de dot mezzo at oslandia dot com, jean dot felder at oslandia dot com

Comment on lines +5 to +6
copyright : (C) 2025 Oslandia
email : benoit dot de dot mezzo at oslandia dot com
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
copyright : (C) 2025 Oslandia
email : benoit dot de dot mezzo at oslandia dot com
Copyright : (C) 2025 by Oslandia
Email : benoit dot de dot mezzo at oslandia dot com, jean dot felder at oslandia dot com

@nyalldawson nyalldawson added the Freeze Exempt Feature Freeze exemption granted label Jan 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

The QGIS project highly values your contribution and would love to see this work merged! Unfortunately this PR has not had any activity in the last 14 days and is being automatically marked as "stale". If you think this pull request should be merged, please check

  • that all unit tests are passing

  • that all comments by reviewers have been addressed

  • that there is enough information for reviewers, in particular

    • link to any issues which this pull request fixes

    • add a description of workflows which this pull request fixes

    • add screenshots if applicable

  • that you have written unit tests where possible
    In case you should have any uncertainty, please leave a comment and we will be happy to help you proceed with this pull request.
    If there is no further activity on this pull request, it will be closed in a week.

@github-actions github-actions bot added the stale Uh oh! Seems this work is abandoned, and the PR is about to close. label Feb 11, 2026
@ptitjano ptitjano removed the stale Uh oh! Seems this work is abandoned, and the PR is about to close. label Feb 11, 2026
@nyalldawson nyalldawson added Frozen Feature freeze - Do not merge! and removed Freeze Exempt Feature Freeze exemption granted labels Feb 20, 2026
@nyalldawson nyalldawson removed the Frozen Feature freeze - Do not merge! label Mar 10, 2026
@github-actions
Copy link
Copy Markdown
Contributor

The QGIS project highly values your contribution and would love to see this work merged! Unfortunately this PR has not had any activity in the last 14 days and is being automatically marked as "stale". If you think this pull request should be merged, please check

  • that all unit tests are passing

  • that all comments by reviewers have been addressed

  • that there is enough information for reviewers, in particular

    • link to any issues which this pull request fixes

    • add a description of workflows which this pull request fixes

    • add screenshots if applicable

  • that you have written unit tests where possible
    In case you should have any uncertainty, please leave a comment and we will be happy to help you proceed with this pull request.
    If there is no further activity on this pull request, it will be closed in a week.

@github-actions github-actions bot added the stale Uh oh! Seems this work is abandoned, and the PR is about to close. label Mar 24, 2026
@ptitjano ptitjano removed the stale Uh oh! Seems this work is abandoned, and the PR is about to close. label Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3D Relates to QGIS' 3D engine or rendering Feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants