Skip to content

CallBack

vtk-examples/Cxx/Interaction/CallBack


Description

Demonstrate how to set up a callback with client data

Getting the camera orientation after interacting with the image is used as an example.

We define a callback passing the active camera as client data and linking the callback to the EndInteractionEvent of the vtkRenderWindowInteractor class. This allows us to get the camera orientation after we manipulate the image. We can then copy/paste this data as needed into our camera to set up a nice initial orientation as shown in the example.

To help orient the cone, we use a vtkOrientationMarkerWidget and a vtkOutlineFilter.

C++

There are two methodologies in C++.

  1. Create a class that inherits from vtkCallbackCommand reimplementing Execute( vtkObject *caller, unsigned long evId, void*) and setting pointers to a client and/or call data as needed. When the class is implemented, it becomes the callback function.
  2. Create a function with this signature: void f( vtkObject * caller, long unsigned int evId, void* clientData, void* callData) and, where needed, create a vtkCallbackCommand setting its callback to the function we have created.

The example demonstrates both approaches.

In the function PrintCameraOrientation note how we convert an array to a vector and get a comma-separated list.

Python

In Python the approach is even simpler. We simply define a function to use as the callback with this signature: def MyCallback(obj, ev):. Then, to pass client data to it, we simply do: MyCallback.myClientData = myClientData. This relies on the fact that Python functions are in fact objects and we are simply adding new attributes such as myClientData in this case.

An alternative method is to define a class passsing the needed variables in the __init__ function and then implement a _call__ function that does the work.

Both approaches are demonstrated in the example.

Other languages

See (Python)

Question

If you have a question about this example, please use the VTK Discourse Forum

Code

CallBack.cxx

/*
Demonstrate the use of a callback.

We also add call data.

*/

#include <vtkActor.h>
#include <vtkAxesActor.h>
#include <vtkCallbackCommand.h>
#include <vtkCamera.h>
#include <vtkConeSource.h>
#include <vtkNamedColors.h>
#include <vtkNew.h>
#include <vtkOrientationMarkerWidget.h>
#include <vtkOutlineFilter.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>

#include <iterator>
#include <sstream>
#include <vector>

#define USE_CALLBACKCOMMAND_CLASS
// Uncomment this if you want to use the function instead.
// #undef USE_CALLBACKCOMMAND_CLASS

namespace {
void PrintCameraOrientation(vtkCamera* cam);
void MakeAxesActor(vtkAxesActor* axesActor);

#if defined(USE_CALLBACKCOMMAND_CLASS)
/**
Here we inherit from vtkCallbackCommand and set pointers to any
client and/or call data as needed.
When the class is implemented, it becomes the callback function.
*/
class CameraModifiedCallback : public vtkCallbackCommand
{
public:
  static CameraModifiedCallback* New()
  {
    return new CameraModifiedCallback;
  }
  // Here we Create a vtkCallbackCommand and reimplement it.
  void Execute(vtkObject* caller, unsigned long evId, void*) override
  {
    // Note the use of reinterpret_cast to cast the caller to the expected type.
    auto interactor = reinterpret_cast<vtkRenderWindowInteractor*>(caller);
    // Just do this to demonstrate who called callback and the event that
    // triggered it.
    std::cout << interactor->GetClassName() << "  Event Id: " << evId
              << std::endl;

    // Now print the camera orientation.
    PrintCameraOrientation(this->cam);
  }
  CameraModifiedCallback() : cam(nullptr)
  {
  }
  // Set pointers to any clientData or callData here.
  vtkCamera* cam;

private:
  CameraModifiedCallback(const CameraModifiedCallback&) = delete;
  void operator=(const CameraModifiedCallback&) = delete;
};

#else
/**
An alternative method is to create a function with this signature:
void f(vtkObject* caller, long unsigned int evId, void* clientData, void*
callData)
and, where needed, create a vtkCallbackCommand setting its callback to the
function we have created.
*/
void vtkCallbackFunc(vtkObject* caller, long unsigned int evId,
                     void* clientData, void* /*callData*/)
{
  // Note the use of reinterpret_cast to cast the caller and callData to the
  // expected types.
  auto interactor = reinterpret_cast<vtkRenderWindowInteractor*>(caller);
  std::cout << interactor->GetClassName() << "  Event Id: " << evId
            << std::endl;
  auto cam = reinterpret_cast<vtkCamera*>(clientData);

  // Now print the camera orientation.
  PrintCameraOrientation(cam);
};
#endif
} // namespace

int main(int, char*[])
{

  vtkNew<vtkNamedColors> colors;

  // Create the Renderer, RenderWindow and RenderWindowInteractor.
  vtkNew<vtkRenderer> ren;
  vtkNew<vtkRenderWindow> renWin;
  renWin->AddRenderer(ren);
  vtkNew<vtkRenderWindowInteractor> iren;
  iren->SetRenderWindow(renWin);

  // Use a cone as a source.
  vtkNew<vtkConeSource> source;
  source->SetCenter(0, 0, 0);
  source->SetRadius(1);
  // Use the golden ratio for the height. Because we can!
  source->SetHeight(1.6180339887498948482);
  source->SetResolution(128);
  source->Update();

  // Pipeline
  vtkNew<vtkPolyDataMapper> mapper;
  mapper->SetInputConnection(source->GetOutputPort());
  vtkNew<vtkActor> actor;
  actor->SetMapper(mapper);
  actor->GetProperty()->SetColor(colors->GetColor3d("peacock").GetData());
  // Lighting
  actor->GetProperty()->SetAmbient(0.3);
  actor->GetProperty()->SetDiffuse(0.0);
  actor->GetProperty()->SetSpecular(1.0);
  actor->GetProperty()->SetSpecularPower(20.0);

  // Get an outline of the data set for context.
  vtkNew<vtkOutlineFilter> outline;
  outline->SetInputData(source->GetOutput());
  vtkNew<vtkPolyDataMapper> outlineMapper;
  outlineMapper->SetInputConnection(outline->GetOutputPort());
  vtkNew<vtkActor> outlineActor;
  outlineActor->SetMapper(outlineMapper);
  outlineActor->GetProperty()->SetColor(colors->GetColor3d("Black").GetData());

  // Add the actors to the renderer, set the background and size.
  ren->AddActor(actor);

  ren->AddActor(outlineActor);
  ren->SetBackground(colors->GetColor3d("AliceBlue").GetData());
  renWin->SetSize(512, 512);

  // Set up a nice camera position.
  vtkNew<vtkCamera> camera;
  camera->SetPosition(4.6, -2.0, 3.8);
  camera->SetFocalPoint(0.0, 0.0, 0.0);
  camera->SetClippingRange(3.2, 10.2);
  camera->SetViewUp(0.3, 1.0, 0.13);
  ren->SetActiveCamera(camera);

  renWin->Render();
  renWin->SetWindowName("CallBack");

  vtkNew<vtkAxesActor> axes;
  MakeAxesActor(axes);
  vtkNew<vtkOrientationMarkerWidget> om;
  om->SetOrientationMarker(axes);
  // Position lower left in the viewport.
  om->SetViewport(0, 0, 0.2, 0.2);
  om->SetInteractor(iren);
  om->EnabledOn();
  om->InteractiveOn();

#if defined(USE_CALLBACKCOMMAND_CLASS)
  // When we implement the class, it automatically becomes the callback
  // function.
  vtkNew<CameraModifiedCallback> getOrientation;
  // Set the camera to use.
  getOrientation->cam = ren->GetActiveCamera();
#else
  // Create the vtkCallbackCommand.
  vtkNew<vtkCallbackCommand> getOrientation;
  // Set the callback to the function we created.
  getOrientation->SetCallback(vtkCallbackFunc);
  // Set the client data.
  getOrientation->SetClientData(ren->GetActiveCamera());
#endif
  iren->AddObserver(vtkCommand::EndInteractionEvent, getOrientation);
  iren->Initialize();
  iren->Start();

  return EXIT_SUCCESS;
}

namespace {
void MakeAxesActor(vtkAxesActor* axes)
{
  axes->SetShaftTypeToCylinder();
  axes->SetXAxisLabelText("X");
  axes->SetYAxisLabelText("Y");
  axes->SetZAxisLabelText("Z");
  axes->SetTotalLength(1.0, 1.0, 1.0);
  axes->SetCylinderRadius(0.5 * axes->GetCylinderRadius());
  axes->SetConeRadius(1.025 * axes->GetConeRadius());
  axes->SetSphereRadius(1.5 * axes->GetSphereRadius());
}

/**
Get a comma separated list.
*/
template <typename T> std::string CommaSeparatedList(std::vector<T> v)
{
  std::ostringstream os;
  std::copy(v.begin(), v.end() - 1, std::ostream_iterator<T>(os, ", "));
  os << v.back();
  return os.str();
}

/**
Print the camera orientation.
*/
void PrintCameraOrientation(vtkCamera* cam)
{
  auto width = 16;
  double pos[3];
  cam->GetPosition(pos);
  double fp[3];
  cam->GetFocalPoint(fp);
  double vu[3];
  cam->GetViewUp(vu);
  double cr[2];
  cam->GetClippingRange(cr);
  std::cout << setw(width) << "Position: "
            << CommaSeparatedList(std::vector<double>(pos, pos + 3))
            << std::endl;
  std::cout << setw(width) << "Focal point: "
            << CommaSeparatedList(std::vector<double>(fp, fp + 3)) << std::endl;
  std::cout << setw(width) << "Clipping range: "
            << CommaSeparatedList(std::vector<double>(cr, cr + 2)) << std::endl;
  std::cout << setw(width) << "View up: "
            << CommaSeparatedList(std::vector<double>(vu, vu + 3)) << std::endl;
  std::cout << setw(width) << "Distance: " << cam->GetDistance() << std::endl;
};
} // namespace

CMakeLists.txt

cmake_minimum_required(VERSION 3.12 FATAL_ERROR)

project(CallBack)

find_package(VTK COMPONENTS 
  CommonColor
  CommonCore
  FiltersModeling
  FiltersSources
  InteractionStyle
  InteractionWidgets
  RenderingAnnotation
  RenderingContextOpenGL2
  RenderingCore
  RenderingFreeType
  RenderingGL2PSOpenGL2
  RenderingOpenGL2
)

if (NOT VTK_FOUND)
  message(FATAL_ERROR "CallBack: Unable to find the VTK build folder.")
endif()

# Prevent a "command line is too long" failure in Windows.
set(CMAKE_NINJA_FORCE_RESPONSE_FILE "ON" CACHE BOOL "Force Ninja to use response files.")
add_executable(CallBack MACOSX_BUNDLE CallBack.cxx )
  target_link_libraries(CallBack PRIVATE ${VTK_LIBRARIES}
)
# vtk_module_autoinit is needed
vtk_module_autoinit(
  TARGETS CallBack
  MODULES ${VTK_LIBRARIES}
)

Download and Build CallBack

Click here to download CallBack and its CMakeLists.txt file. Once the tarball CallBack.tar has been downloaded and extracted,

cd CallBack/build

If VTK is installed:

cmake ..

If VTK is not installed but compiled on your system, you will need to specify the path to your VTK build:

cmake -DVTK_DIR:PATH=/home/me/vtk_build ..

Build the project:

make

and run it:

./CallBack

WINDOWS USERS

Be sure to add the VTK bin directory to your path. This will resolve the VTK dll's at run time.