Create gridlines in 3D view, similar to the effect in the figure

It would be quite straightforward to implement this, but we would need to understand what is the main motivation. We would not want to invest into implementing a tool if existing measurement tools would work just as well or better. 3D markups are generally more accurate, reliable, verifiable measurements than counting of gridlines. For approximate appreciation of size, you can enable view ruler.

In certain fields, current practice may be to count grid points and it may take time to convince people to use proper 3D measurements instead. For this transition period (or to prove that 3D markups are superior), it could make sense to use a small Python script that displays the grid. For example, you can copy-paste this code snippet to the Python console to show a 10mm grid in the first 3D view:

threeDViewIndex = 0
gridSizeMm = 10
renderers = slicer.app.layoutManager().threeDWidget(threeDViewIndex).threeDView().renderWindow().GetRenderers()
threeDRenderer = renderers.GetItemAsObject(0)
rulerRenderer = renderers.GetItemAsObject(1)

# Create a plane source
planeSource = vtk.vtkPlaneSource()
planeMapper = vtk.vtkPolyDataMapper()
planeMapper.SetInputConnection(planeSource.GetOutputPort())
planeActor = vtk.vtkActor()
planeActor.SetMapper(planeMapper)
planeActor.GetProperty().SetColor(0, 0, 0)
planeActor.GetProperty().SetOpacity(0.3)
planeActor.GetProperty().SetRepresentationToWireframe()
rulerRenderer.AddActor(planeActor)

camera = threeDRenderer.GetActiveCamera()

def updateScale(caller=None, eventId=None):
    if not camera.GetParallelProjection():
        print("Set view to parallel projection")
        return
    # View: xmin, ymin, xmax, ymax; range: -1 to +1; origin is bottom left
    # Determine the available renderer size in millimeters
    minWorld = [vtk.reference(-1), vtk.reference(-1), vtk.reference(-1)]
    rulerRenderer.ViewToWorld(*minWorld)
    maxWorld = [vtk.reference(1), vtk.reference(1), vtk.reference(1)]
    rulerRenderer.ViewToWorld(*maxWorld)
    rendererSizeInWorld = [maxWorld[0]-minWorld[0], maxWorld[1]-minWorld[1]]
    # Parallel scale: height of the half viewport in world-coordinate distances.
    scalingFactorMmPerWorld = camera.GetParallelScale() * 2.0 / rendererSizeInWorld[1]
    gridSizeWorld = gridSizeMm / scalingFactorMmPerWorld
    numberOfGrids = [int(rendererSizeInWorld[0] / gridSizeWorld / 4) * 2 + 2, int(rendererSizeInWorld[1]/gridSizeWorld/4) * 2 + 2]
    lowerLeft = [-numberOfGrids[0] * gridSizeWorld, -numberOfGrids[1] * gridSizeWorld]
    upperRight = [numberOfGrids[0] * gridSizeWorld, numberOfGrids[1] * gridSizeWorld]
    planeSource.SetOrigin(lowerLeft[0], lowerLeft[1], 0)
    planeSource.SetPoint1(upperRight[0], lowerLeft[1], 0)
    planeSource.SetPoint2(lowerLeft[0], upperRight[1], 0)
    planeSource.SetResolution(numberOfGrids[0] * 2, numberOfGrids[1] * 2)

cameraObservation = camera.AddObserver(vtk.vtkCommand.ModifiedEvent, updateScale)
updateScale()
slicer.util.forceRenderAllViews()

# Run these lines to hide the grid:
#
# camera.RemoveObserver(cameraObservation)
# rulerRenderer.RemoveActor(planeActor)
#

@muratmaga If you find this useful then you may consider adding it to a module in SlicerMorph.

1 Like

Thanks this might be useful option for people who want to have grid. We don’t use it but can be added as slicermorph customization.

1 Like