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:
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
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.
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:
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:
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.
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.