for(vtkDataObject *ds : vtk::Range(multiblock_ds)) { .. }

Similar to the vtk::DataArrayTupleRange, vtk::DataArrayValueRange, and vtk::Range(vtkCollection*) helpers added recently, it’s the vtkCompositeDataSet hierarchy’s turn to get C++11 for-range support:

https://gitlab.kitware.com/vtk/vtk/merge_requests/5158

Compared with the usual way of traversing a multiblock dataset (vtkCompositeDataSetIterator / vtkDataObjectTreeIterator), the new syntax is much easier to use. For example, computing the bounds of a vtkMultiBlockDataSet used to look something like this:

#include <vtkDataObjectTreeIterator.h>

auto *iter = mbds->NewTreeIterator();                 // Get iterator
iter->SkipEmptyNodesOn();                             // Configure
iter->TraverseSubTreeOn();
iter->VisitOnlyLeavesOn();
for (iter->InitTraversal();                           // Traverse
     !iter->IsDoneWithTraversal();
     iter->GoToNextItem())
{
  vtkDataObject *dObj = iter->GetCurrentDataObject(); // Dereference
  /* Accumulate dObj's bounds */                      // Work
}
iter->Delete();                                       // Clean-up

Compare this with the new for-range equivalent:

#include <vtkDataObjectTreeRange.h>

using Opts = vtk::DataObjectTreeOptions;
for (vtkDataObject *dObj : vtk::Range(mbds, Opts::SkipEmptyNodes |
                                            Opts::TraverseSubTree |
                                            Opts::VisitOnlyLeaves))
{
  /* Accumulate dObj's bounds */
}
  • The code is safer – less complexity means less to mess up:
    • There’s no need to manage the lifetime of the iterators. They are scoped to the for-loop and clean themselves up when destroyed. The only objects that exist in the new code are the input multiblock and the current leaf data object.
    • The old-style VTK iterator API is error prone for me – I tend to forget to negate the result of IsDoneWithTraversal() and things like that when using it. It’s too verbose for a very simple, common operation.
  • The traversal options offered by the older iterators (SkipEmptyNodes, TraverseSubTree, VisitOnlyLeaves) are provided as enum flags that are passed into the vtk::Range function.
  • The loop syntax is much, much easier to write.
  • The intent of the loop is also easier to read – less clutter, more code that matters.

It is worthwhile to note that these do not replace the old iterators completely. They cannot be used with the vtkCompositeDataSet API to modify the structure of the dataset, nor do they provide the CurrentFlatIndex information. They are simply a convenient view into the component datasets.
Edit: This functionality was added in a followup patch. See the comments below.

4 Likes

Nice! It can really alleviate the headache when traversing the vtk compsite dataset. I can help to convert the vtk examples once it’s merged.

What’s the road block for using it to modify the structure of the dataset? For now in VTK, what’s the canonical way to modify a dataset(I assume through some existing filter)?

Right now, if all you have is a vtkCompositeDataSet or vtkDataObjectTree pointer, the dataset’s structure can only be modified using these iterators, since there is no other API for setting the internal datasets:

https://vtk.org/doc/nightly/html/classvtkCompositeDataSet.html
https://vtk.org/doc/nightly/html/classvtkDataObjectTree.html

I have an idea that might actually allow this to work. I’ll see if I can get a patch together in the next few hours.

I have a new MR that extends the existing functionality to do everything the older vtkCompositeDataIterators did, except iterate in reverse:

https://gitlab.kitware.com/vtk/vtk/merge_requests/5201

The iterators now return a reference proxy object, instead of vtkDataObject*. This proxy may be used like a pointer, in which case it will forward the current vtkDataObject*. This means that the following code is still legal:

for (auto node : vtk::Range(cds))                                            
{ // decltype(node) == CompositeDataSetNodeReference                         
  if (node)                  // same as: if (node.GetDataObject() != nullptr)
  {                                                                          
    assert(node->IsA("vtkDataObject"));     // node.GetDataObject()->IsA(...)
    node = nullptr;                         // node.SetDataObject(nullptr)   
  }                                                                          
}                                                                            
                                                                             
for (vtkDataObject *dObj : vtk::Range(cds))                                  
{                                                                            
  // Work with dObj                                                          
}                                                                            

This allows for simple access to the objects in the composite dataset. If more advanced operations are required, the CompositeDataSetNodeReference can:

  • Access the current vtkDataObject*:
    • vtkDataObject* NodeReference::GetDataObject() const
    • NodeReference::operator vtkDataObject* () const (implicit conversion)
    • vtkDataObject* NodeReference::operator->() const (arrow operator)
  • Replace the current vtkDataObject* in the composite dataset:
    • void NodeReference::SetDataObject(vtkDataObject*)
    • NodeReference& NodeReference::operator=(vtkDataObject*) (assignment)
  • SetGet the vtkDataObject at the same position in another composite dataset
    • void NodeReference::SetDataObject(vtkCompositeDataSet*, vtkDataObject*)
    • vtkDataObject* NodeReference::GetDataObject(vtkCompositeDataSet*) const
  • Check and access node metadata (if any):
    • bool NodeReference::HasMetaData() const
    • vtkInformation* NodeReference::GetMetaData() const
  • Get the current flat index within the parent range:
    • unsigned int NodeReference::GetFlatIndex() const

The NodeReference shares state with the OwnerType iterator that generates it. Incrementing or destroying the parent iterator will invalidate the reference. In debugging builds, these misuses will be caught via runtime assertions.

Several usages of vtkCompositeDataIterator have been ported to use the new range iteration syntax to improve testing and provide some examples. The unit tests for these ranges also show example usages of the new functionality.

1 Like

Will there be a blog at some point ?

Nice work !

Thanks! No plans for a blog right now, but maybe after the implementation settles a bit I will.

1 Like

The pull request to update VTK examples is here. One thing I found is that you can not mix vtkCompositeDataSet/vtkDataObjectTree with vtkDataObjectTreeIterator/vtkCompositeDataSetIterator. And I think it makes sense which can catch run time error at compile time.