Need help on implementing slice visibility of a 3d model

Hello everyone,

I am new to Slicer, and I have gone through some tutorial PDFs provided in the developer section of the official site. I have created a scripted module using the Extension Wizard. For my module, I am implementing functionality that allows visualization of the slice visibility of a 3D model, specifically the slice intersection.

I found that the class vtkMRMLModelSliceDisplayableManager is responsible for slice visibility when it is enabled in the Models module. Based on this, I created a rendering pipeline. However, the actor is not visible. Previously, it was visible but not correctly positioned in the slice views—it appeared in the bottom-left corner of the Axial view.

Here is my code:

class SliceVisibilityPipeline(VTKObservationMixin):
    def __init__(self, modelNode, sliceViewName='Red'):
        VTKObservationMixin.__init__(self)

        self.modelNode = modelNode
        self.modelDisplayNode = self.modelNode.GetDisplayNode()
        self.sliceViewName = sliceViewName
        
        # Get slice node and renderer
        layoutManager = slicer.app.layoutManager()
        self.sliceWidget = layoutManager.sliceWidget(self.sliceViewName)
        self.sliceView = self.sliceWidget.sliceView()
        self.renderWindow = self.sliceView.renderWindow()
        self.renderer = self.renderWindow.GetRenderers().GetFirstRenderer()

        self.sliceLogic = self.sliceWidget.sliceLogic()
        self.sliceNode = self.sliceLogic.GetSliceNode()
        
        # Create pipeline
        self.plane = vtk.vtkPlane()
        self.nodeToWorld = vtk.vtkGeneralTransform()
        
        # Model warper
        self.modelWarper = vtk.vtkTransformFilter()
        self.modelWarper.SetTransform(self.nodeToWorld)

        # Plane cutter
        self.cutter = vtk.vtkPlaneCutter()
        self.cutter.SetPlane(self.plane)
        self.cutter.SetInputConnection(self.modelWarper.GetOutputPort())

        # Transform to slice space
        self.transformToSlice = vtk.vtkTransform()
        self.transformer = vtk.vtkTransformPolyDataFilter()
        self.transformer.SetTransform(self.transformToSlice)
        self.transformer.SetInputConnection(self.cutter.GetOutputPort())

        # Set up mapper and actor for display
        self.mapper = vtk.vtkPolyDataMapper2D()
        self.mapper.SetInputConnection(self.transformer.GetOutputPort())
        self.mapper.ScalarVisibilityOff()

        self.actor = vtk.vtkActor2D()
        self.actor.SetMapper(self.mapper)
        self.actor.SetVisibility(0)

        # Add actor to renderer
        self.renderer.AddActor2D(self.actor)

        # Set up initial pipeline
        self.updatePipeline()

        # Observe slice node changes
        self.addObserver(self.sliceNode, vtk.vtkCommand.ModifiedEvent, self.onSliceModified)

    def updatePipeline(self):
        self.cleanup()

        if not self.modelNode or not self.modelDisplayNode:
            print("No model or display node found.")
            self.actor.SetVisibility(False)
            return
        
        # Get the mesh data
        polyData = self.modelNode.GetPolyData()
        if not polyData or polyData.GetNumberOfPoints() == 0:
            print("No points are found in model")
            self.actor.SetVisibility(False)
            return
        
        # Update the model warper input
        self.modelWarper.SetInputData(polyData)

        # Get transform from model to world
        if self.modelNode.GetParentTransformNode():
            self.modelNode.GetParentTransformNode().GetTransformToWorld(self.nodeToWorld)
        else:
            self.nodeToWorld.Identity()

        # Update the slice plane based on current slice position
        sliceXYToRAS = vtk.vtkMatrix4x4()
        sliceXYToRAS.DeepCopy(self.sliceNode.GetXYToRAS())
        self.updateSlicePlane(sliceXYToRAS, self.plane)

        # Update the transform from RAS to slice XY
        rasToSliceXY = vtk.vtkMatrix4x4()
        vtk.vtkMatrix4x4.Invert(sliceXYToRAS, rasToSliceXY)
        self.transformToSlice.SetMatrix(rasToSliceXY)

        # Force update of the pipeline to check if there are any points
        self.cutter.Update()
        if self.cutter.GetOutput().GetNumberOfPoints() < 1:
            print("No intersection points found")
            self.actor.SetVisibility(False)
            return
        
        # Update actor properties
        actorProperty = self.actor.GetProperty()
        actorProperty.SetColor(self.modelDisplayNode.GetColor())
        actorProperty.SetLineWidth(self.modelDisplayNode.GetSliceIntersectionThickness())
        actorProperty.SetPointSize(self.modelDisplayNode.GetSliceIntersectionThickness())
        actorProperty.SetOpacity(self.modelDisplayNode.GetSliceIntersectionOpacity())

        # Make actor visible
        self.actor.SetVisibility(True)

        # Force render update
        self.renderWindow.Render()
        
        # Observe slice movements to update plane and cutter
        self.sliceNodeObserverTag = self.sliceNode.AddObserver(vtk.vtkCommand.ModifiedEvent, self.onSliceModified)

    def updateSlicePlane(self, sliceMatrix, plane):
        normal = [0, 0, 0]
        origin = [0, 0, 0]

        for i in range(3):
            normal[i] = sliceMatrix.GetElement(i, 2)
            origin[i] = sliceMatrix.GetElement(i, 3)
        
        vtk.vtkMath.Normalize(normal)
        plane.SetNormal(normal)
        plane.SetOrigin(origin)

    def onSliceModified(self, caller, event):
        # Update the slice plane when the slice changes
        self.updatePipeline()

    def cleanup(self):
        if hasattr(self, 'sliceNode') and hasattr(self, 'sliceNodeObserverTag'):
            self.sliceNode.RemoveObserver(self.sliceNodeObserverTag)
        if hasattr(self, 'renderer') and hasattr(self, 'actor'):
            self.renderer.RemoveActor2D(self.actor)
            self.renderWindow.Render()

I suspect the issue might lie in the transformation, though I am not sure. Could anyone please help me identify where I might be making a mistake?

Thanks.

My obvious question is why you are implementing this? This is a feature already existing in Slicer. You just need to use these functions of the display node of your model node:

Slicer has been created with probably millions of hours of more than a hundred programmers, have been thoroughly tested and proven, and a multitude of special cases and complicated geometry issues have been addressed in the past years. I really suggest not reinventing the wheel, just using what is available.

Thank you for your response — I completely agree and appreciate the immense work that has gone into Slicer over the years.

I’m aware that slice intersection visibility is already built-in via the display node, and I fully intend to use those in actual projects. However, I’m reimplementing a basic version purely for learning purposes — to better understand how the rendering pipeline, transforms, and VTK/MRML components work together.

I’m not aiming to replicate full functionality, just exploring the basics to deepen my understanding. I really appreciate your guidance and insights.

Thanks again!