Add an actor (vtkCubeAxesActor) to the scene in C++

Hi,

I have some understanding how to add vtkObject inherited objects as described here via MRML nodes but I can’t find a way to add vtkCubeAxesActor (and in general vtkActor).

Though I have found some information on that in script repository but when I by analogy do:

  vtkMRMLAbstractDisplayableManager* modelDisplayableManager =
      LayoutManager->threeDWidget(0)
          ->threeDView()
          ->displayableManagerByClassName("vtkMRMLModelDisplayableManager");

I cannot somehow cast vtkMRMLAbstractDisplayableManager to vtkMRMLModelDisplayableManager as there is no vtkMRMLModelDisplayableManager.h header (neither there is vtkMRMLAbstractDisplayableManager.h).

Slicer uses vtkMRML...DisplayableManager classes to create actors and add them to renderers. So, if you want to add actors that none of the existing displayable managers support then you need to add your own displayable manager and you may also add MRML classew (to store data and display options in the scene).

What would you like to use a cube axis actor for? If you just want to display orientation then you can enable orientation marker display, which can use a box or arrows axis actor or an arbitrary 3D model (by default a human body). You can specify the style and size and customize the axis letters in the view node.

1 Like

I wanted to display coordinates in 3d view. Not only the directions (wich is good by the way) but also XYZ values, like scales. I work in geographic coordinates so I would like to check correctness of my code. Usually the main object should be vtkImageData and display scales around it would be fine. Probably there are another options to achieve my goal?

If I create vtkMRMLCubeAxesActorDisplayableManager (I guess I would need to inherit from some base class) and create a button on 3d view toolbar that shows/hides vtkCubeAxesActor around chosen object would that be enough? Or the task is more difficult than I think?

With the recent knowlegde about vtkCubeAxesActor transform problem I still want to have something like visualized coordinate system in 3D view but now I’m not much sticked to the vtkCubeAxesActor. Probably there are some options to achieve the goal without using vtkCubeAxesActor?
Maybe I could create 8 points at vtkImageData's corners and display coordinates only at these points…

Also is it ok that if I add vtkCubeAxesActor then the interaction perfomance noticeably decreases? Any movements becomes much slower

I’m getting known with DisplayableManager.
From SlicerAstro I took vtkMRMLAstroTwoDAxesDisplayableManager as an example and simplified it.
According to the documentation and SlicerAstro too I need to register my disp manager:

void qSlicerStackLoadModule::setup()
{
  this->Superclass::setup();
  ...
  vtkMRMLSliceViewDisplayableManagerFactory::GetInstance()->
    RegisterDisplayableManager("vtkMRMLColadaCubeAxesDisplayableManager");
}

But I noticed that generated vtkSlicerStackLoadModuleMRMLDisplayableManagerObjectFactory.cxx does some checks and the debugging helped me to understand that bool vtkMRMLDisplayableManagerGroup::IsADisplayableManager(const char* displayableManagerName) returns false:

bool vtkMRMLDisplayableManagerGroup
::IsADisplayableManager(const char* displayableManagerName)
{
  // Check if displayableManagerName is a valid displayable manager
  vtkSmartPointer<vtkObject> objectSmartPointer;
  objectSmartPointer.TakeReference(vtkObjectFactory::CreateInstance(displayableManagerName));
  if (objectSmartPointer.GetPointer() &&
      objectSmartPointer->IsA("vtkMRMLAbstractDisplayableManager"))
    {
    return true;    // the execution can't reach there as `objectSmartPointer` is NULL
    }
#ifdef VTK_DEBUG_LEAKS
  vtkDebugLeaks::DestructClass(displayableManagerName);
#endif
#ifdef MRMLDisplayableManager_USE_PYTHON
  // Check if vtkClassOrScriptName is a python script
  if (std::string(displayableManagerName).find(".py") != std::string::npos)
    {
    // TODO Make sure the file exists ...
    return true;
    }
#endif
  return false;
}

The var displayableManagerName is vtkMRMLColadaCubeAxesDisplayableManager.
It seems that I my disp manager doesn’t follow some conditions of a disp manager but I don’t understand what are these conditions…

It is late night and tomorrow I will try to investigate the problem more precisely. But probably you have experience and already know where to look…

Here is the header file of my disp manager:

#ifndef __vtkMRMLColadaCubeAxesDisplayableManager_h
#define __vtkMRMLColadaCubeAxesDisplayableManager_h

// MRMLDisplayableManager includes
#include "vtkMRMLAbstractDisplayableManager.h"
#include <vtkSlicerStackLoadModuleMRMLDisplayableManagerExport.h>

// Qt declaration
class QColor;

/// \brief Displayable manager that displays 2D WCS axes in 2D view
class VTK_MRMLDISPLAYABLEMANAGER_COLADA_EXPORT vtkMRMLColadaCubeAxesDisplayableManager
  : public vtkMRMLAbstractDisplayableManager
{
  friend class vtkColadaCubeAxesRendererUpdateObserver;

public:
  static vtkMRMLColadaCubeAxesDisplayableManager* New();
  vtkTypeMacro(vtkMRMLColadaCubeAxesDisplayableManager,vtkMRMLAbstractDisplayableManager);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /// Get vtkMarkerRenderer
  vtkRenderer* vtkMarkerRenderer();

  /// Set annotation color
  void SetAnnotationsColor(double red,
                           double green,
                           double blue);

  /// Set font style
  void SetAnnotationsFontStyle(const char* font);

  /// Set font size
  void SetAnnotationsFontSize(int size);

protected:

  vtkMRMLColadaCubeAxesDisplayableManager();
  virtual ~vtkMRMLColadaCubeAxesDisplayableManager();

  /// Observe the View node and initialize the renderer accordingly.
  virtual void Create() override;

  /// Called each time the view node is modified.
  /// Internally update the renderer from the view node
  /// \sa UpdateFromMRMLViewNode()
  virtual void OnMRMLDisplayableNodeModifiedEvent(vtkObject* caller) override;

  /// Update the renderer from the view node properties.
  void UpdateFromViewNode();

  /// Update the renderer based on the master renderer (the one that the orientation marker follows)
  void UpdateFromRenderer();

private:

  vtkMRMLColadaCubeAxesDisplayableManager(const vtkMRMLColadaCubeAxesDisplayableManager&);// Not implemented
  void operator=(const vtkMRMLColadaCubeAxesDisplayableManager&); // Not Implemented

  class vtkInternal;
  vtkInternal * Internal;
};

#endif

I have found the problem.
When VTK iterates over the factories (in vtkObject* vtkObjectFactory::CreateInstance(const char* vtkclassname, bool)), none of them is able to create instance of vtkMRMLColadaCubeAxesDisplayableManager.

The solution I have not found yet…

I noticed that vtkSlicerStackLoadModuleMRMLDisplayableManagerObjectFactory object factory is not registered even I use SlicerConfigureDisplayableManagerObjectFactory(...) cmake macro and there is vtkSlicerStackLoadModuleMRMLDisplayableManagerObjectFactory.h and vtkSlicerStackLoadModuleMRMLDisplayableManagerObjectFactory.cxx generated files in build dir.

Finally I have found my drawback. I should have been put these two lines of code:

// DisplayableManager initialization
#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkSlicerStackLoadModuleMRMLDisplayableManager)

to the qSlicer${MODULE_NAME}Module.cxx.
Now both ...DisplayableManagerObjectFactory and ...DisplayableManager are registered.

As a summary to register DisplayableManager I needed:

  • copy MRMLDM folder from any built-in Slicer module (I used Transforms module) to my module and customize CMakeLists.txt (and of course customize .cxx and .h files);
  • add this MRMLDM target to MODULE_TARGET_LIBRARIES of the main module CMakeLists.txt;

and in qSlicer...Module.cxx:

  • #include “vtkMRMLSliceViewDisplayableManagerFactory.h” // for registering Disp Manager in SliceView
  • #include “vtkMRMLThreeDViewDisplayableManagerFactory.h” // for registering Disp Manager in Three D view
  • #include <vtkAutoInit.h>
  • VTK_MODULE_INIT(…DisplayableManager) // without this the ...ObjectFactory won’t be registered

in setup() method:

  • bool val = vtkMRMLThreeDViewDisplayableManagerFactory::GetInstance()->RegisterDisplayableManager("…DisplayableManager");

Thank you for sharing your progress and solution. In general, it is a good idea to clone an existing working module or component and modify it.

VTK9’s build system became really complicated, so you need to do “magic” things like these VTK_MODULE_INIT macros (magic = more complicated than most people, including myself, would want to spend time with to understand).

1 Like

I need now to get current node (the last node the user interacted with and that is displayed) and create around it vtkCubeAxesActor. Thus I need to retrieve bounds from the node.

To achieve that I think I could do something like (in class inherited from vtkMRMLAbstractThreeDViewDisplayableManager):

  vtkMRMLVolumeNode* volumeNode = vtkMRMLVolumeNode::SafeDownCast(GetSelectionNode());  // this gives me an error 
  if (!volumeNode)
    return;
  
  double bounds[6];
  volumeNode->GetBounds(bounds);

but I get the error when I’m trying to downcast:

error: C2440: 'initializing': cannot convert from 'vtkObject *' to 'T *' with [T=vtkMRMLVolumeNode]

I have looked to other modules and it seems they do almost exactly the same for example in
Slicer\Modules\Loadable\VolumeRendering\MRMLDM\vtkMRMLVolumeRenderingDisplayableManager.cxx
line 503
vtkMRMLVolumeNode* volumeNode = vtkMRMLVolumeNode::SafeDownCast( displayNode->GetDisplayableNode());.

Hi,

Is there any difference of developping custom node that adds some vtkActor (vtkCubeAxesActor) to the scene and node that adds objects like volume, polydata, lines, points etc…?

As I know vtkActor should be added directly to renderer, would Scene->addNode(actorNode) work properly in this case?

To add an actor to the renderer, you need to create a new displayable manager class. Responsibility of this class is to observe the MRML scene and add actors to the renderer accordingly. Usually it adds one actor per display node. You register the new displayable manager class in the the displayable manager factory so that the layout manager can add it to each created view.

1 Like

Thank you, this is the way how I implemented it. It works.

1 Like