Using the command pattern to undo our mistakes

Nicanor Romero Venier
BQ Engineering
Published in
4 min readAug 13, 2018

In the previous article we described how Qt was integrated with VTK to create a 3D rendering application. In one of the sections we showed how commands were passed from the main thread to the rendering thread. In this new article we will expand said section, describing how to apply the command pattern to this particular case.

The command pattern is a behavioural design pattern that consists of encapsulating actions within generic objects that will be executed later. This allows us to create different actions from any part of our code and send them to a queue. This queue will then be consumed by an invoker, which has no idea where this command is coming from or what it does. This gives us the advantage of treating all the different actions in the same way, since they will all have the same interface.

In our case, the client creating the commands will be the QVtkFboItem, the invoker will be the QVtkFboRenderer and the receiver will be the ProcessingEngine in most cases.

Interactions in the command pattern

Another advantage of using this pattern is the ease of integrating a QUndoStack. This will allow the user to undo/redo all the actions that are applied to the 3D canvas, such as importing a 3D model or translating it. To achieve this, all our command objects will inherit from QUndoCommand.

class CommandModel : public QUndoCommand

The QUndoCommand provides two virtual methods called undo() and redo(). Using the first one we will implement the particular action we want executed in the canvas. In the redo method, we will implement the action to revert it.

void CommandModelAdd::redo()
{
m_vtkFboRenderer->addModelActor(m_model);
}
void CommandModelAdd::undo()
{
m_vtkFboRenderer->deleteModelActor(m_model);
}

Compared with the code presented in the previous article, here we will add a QUndoStack where the commands will we pushed after being executed. In fact, the QUndoStack runs the redo method automatically when pushing to the stack. Then, when the user undoes an action the command will run the undo method and be popped out of the stack.

The Queue and the Stack

In the case of the Translate command we had to implement a small adaptation. Since the translation is done using the mouse, the objects being translated goes through several transformations that give feedback to the user while dragging the model. However, we do not want all these intermediate commands to be added to the stack, just the command after the user releases the mouse button. In order to achieve this we just added a parameter to the translate command objects that differentiate which ones need to be added to the stack and which ones do not.

if (command->addToStack())
{
m_undoStack->push(command);
}
else
{
command->execute();
}

Also, since the undo/redo actions need to be executed in the renderer thread, these are also implemented as commands. This way they are executed during the render step, just like all the other commands. Fortunately there is another reason why we want this behaviour. Imagine you are working on a really slow computer executing really demanding actions, a situation where the user can generate commands more quickly than the computer can process them. In this case, if an undo action is submitted we want it to be queued as well so that it reverts the last action from the queue and not the last action executed.

Undo/Redo buttons implemented in QML

Another use case where this approach is helpful is when opening a project. A project could consist of several STL files with different instances, different position, rotation angle or scale factor. In this case, all these commands would be created and queued at once to be executed but stacked as a single command. This way, despite having multiple commands, the action of opening a project could be undone with just one click. To achieve this, QUndoStack provides developers with macros.

To further extend the capabilities of this example application we could add commands to rotate, scale or duplicate models. For now, only the Add, Delete and Translate commands have been implemented. You can find the source code in the following repository:

The code was compiled using Qt 5.9.3 and VTK 8.1.1, tested both in Windows 10 and Ubuntu 16.04. Bear in mind that in order for this code to work you will need to add the vtkRenderingExternal module when compiling VTK. For more detailed building instructions check the README file found in the repository.

--

--