Tree edges are rendered incorrectly in vtkCocoa after calling SetLineWidth()

So there appears to be an obscure bug present in the vtkCocoa renderer (used in MacOS) which happens in vtkGraphLayoutView. Modifying the line width of the graph edges breaks the render. It appears that all rectangles in the graph layout are typically internally treated as two triangles, but something is wrong with the creation of these objects in cocoa in which only one triangle is created per edge.

The effect is seen in the screenshot below:

In this example there should be 5 edges, which should be internally comprised of 10 triangles. Only 5 triangles are created in the Cocoa version, which are then laid out expecting 10. This breaks a lot of the subsequent code that handles edge selection / picking as triangles are rendered in the wrong positions.

The correct behaviour is shown in the same example running in a non-cocoa renderer, all 5 edges are shown properly.

Screenshot from 2024-08-20 15-50-28

I’ve written a slightly modified example code to demonstrate the effect, it’ll likely only happen on MacOS.

#!/usr/bin/env python

# noinspection PyUnresolvedReferences
import vtkmodules.vtkInteractionStyle
# noinspection PyUnresolvedReferences
import vtkmodules.vtkRenderingOpenGL2
from vtkmodules.vtkCommonDataModel import (
    vtkMutableDirectedGraph,
    vtkTree
)
from vtkmodules.vtkViewsInfovis import vtkGraphLayoutView

import vtk

def make_theme(use_mellow=True):

    theme = vtk.vtkViewTheme()

    if use_mellow:

        theme.CreateMellowTheme()

        theme.SetLineWidth(45)  # this method shows the effect 

        return theme
    
    else:

        theme.SetVertexLabelColor(0,0,0)
        theme.SetEdgeLabelColor(0,0,0)
        theme.SetPointSize(10)
        theme.SetOutlineColor(vtk.vtkNamedColors().GetColor3d('Black'))
        theme.SetSelectedCellColor(vtk.vtkNamedColors().GetColor3d('Red'))
        theme.SetSelectedPointColor(vtk.vtkNamedColors().GetColor3d('Red'))
        theme.SetPointHueRange(0, 1.0)
        theme.SetPointSaturationRange(0.0, 1.0)
        theme.SetBackgroundColor([0.3,0.3,0.3])
        theme.SetBackgroundColor2([0.7,0.7,0.7])


        theme.SetLineWidth(45)   # this method shows the effect

        return theme



def main():


    graph = vtkMutableDirectedGraph()

    a = graph.AddVertex()
    b = graph.AddChild(a)
    c = graph.AddChild(a)
    d = graph.AddChild(b)
    e = graph.AddChild(c)
    f = graph.AddChild(c)

    vertex_labels = vtk.vtkStringArray()
    vertex_labels.SetName('VertexLabel')
    vertex_labels.InsertValue(a, 'a')
    vertex_labels.InsertValue(b, 'b')
    vertex_labels.InsertValue(c, 'c')
    vertex_labels.InsertValue(d, 'd')
    vertex_labels.InsertValue(e, 'e')
    vertex_labels.InsertValue(f, 'f')
    graph.GetVertexData().AddArray(vertex_labels)

    edge_labels = vtk.vtkStringArray()
    edge_labels.SetName('EdgeLabel')
    edge_labels.InsertValue(graph.GetEdgeId(a, b), 'a -> b')
    edge_labels.InsertValue(graph.GetEdgeId(a, c), 'a -> c')
    edge_labels.InsertValue(graph.GetEdgeId(b, d), 'b -> d')
    edge_labels.InsertValue(graph.GetEdgeId(c, e), 'c -> e')
    edge_labels.InsertValue(graph.GetEdgeId(c, f), 'c -> f')
    graph.GetEdgeData().AddArray(edge_labels)

    tree = vtkTree()
    success = tree.CheckedShallowCopy(graph)
    print('Success?', success)

    view = vtkGraphLayoutView()
    view.SetRepresentationFromInput(tree)

    view.SetLayoutStrategyToTree()

    theme = make_theme(use_mellow=True)

    view.ApplyViewTheme(theme)


    view.SetVertexColorArrayName('VertexDegree')
    view.SetColorVertices(True)
    view.SetVertexLabelArrayName('VertexLabel')
    view.SetVertexLabelVisibility(True)
    view.SetEdgeLabelArrayName('EdgeLabel')
    view.SetEdgeLabelVisibility(True)
    view.SetLayoutStrategyToTree()

    view.ResetCamera()
    view.GetRenderWindow().SetSize(600, 600)
    view.GetRenderWindow().SetWindowName('CreateTree')
    view.GetRenderWindow().Render()
    view.GetInteractor().Initialize()
    view.GetInteractor().Start()


if __name__ == '__main__':
    main()