Programming new apps

In this tutorial, you will learn how to program an App that shakes atoms in a document when the user presses a button.

 

Generating the SAMSON Element

First, use the SAMSON Element Generator to create a new SAMSON Element containing one app class.

Call the SAMSON Element AtomShaker:

 

and let the name of the app be App (we’re creative like that), so that the class name will be SEAtomShakerApp:

 

If necessary, refer to this page for a reminder on how to use the SAMSON Element Generator and set up your programming environment. If everything went well, you should now have a workable project with two main classes:

  • SEAtomShakerApp: the main class, which implements the functionality of the app
  • SEAtomShakerAppGUI: the main GUI, which implements the user interface of the app

Each class has its own pair of hpp (header) and cpp (code) files.

If you build your SAMSON Element and start SAMSON (e.g. the debug version provided with the SDK), you should see your app appear in the App menu:

and the app toolbar, with a default SAMSON icon:

 

What’s in a name?

The first thing we’re going to do is change the name of the app from “SEAtomShakerApp” to “Atom Shaker”. To achieve this, modify the getName function in the SEAtomShakerGUI class, in the SEAtomShakerGUI.cpp file:

QString SEAtomShakerAppGUI::getName() const { 
 
	// SAMSON Element generator pro tip: this string will be the GUI title. 
	// Modify this function to have a user-friendly description of your app inside SAMSON
 
	return "Atom Shaker"; 
 
}

Notice the comment in the function which was suggesting you do just that. In general, you can search for such comments in the code written by the SAMSON Element Generator to get development hints.

Once you compile and run again, the new app name is visible:

(note how the apps are sorted alphabetically.)

If you would like to modify the icon, you should modify the file SEAtomShakerAppIcon.png in the resources/icons folder.

 

Cute as a button

Let’s now add a button to our user interface. Open the SEAtomShakerAppGUI.ui file. Depending on your environment, opening this file should start Qt Designer or Qt Creator. The default user interface created by the SAMSON Element Generator is this:

The default interface contains a label with a developer tip, that you can replace with a push button:

It is also a good habit to give meaningful names to widgets, especially later when you create more complex interfaces, so let’s get into this habit right now:

 

Signals and slots

We need to make our app react when the user releases the push button. To do this, we are going to add a slot to our interface (a slot is a function called when a widget emits a signal):

Finally, we connect the released signal of the push button to this new slot:
We’re done with this interface for now. We can save it and quit the interface editor:

 

Shaking atoms

We can now Harlem shake atoms.

Even for simple apps, it is preferable to separate the user interface from the core functionality of the app. We are thus going to add to the GUI class the slot we promised to the interface editor. First, we declare the new slot in the SEAtomShakerAppGUI.hpp file:

Then, we add the definition of this function in the SEAtomShakerAppGUI.cpp file (for example at the end of the file):

void SEAtomShakerAppGUI::onShakeAtoms() {
 
	getApp()->shakeAtoms();
 
}

Note how this function simply calls the shakeAtoms function of the app class, to delegate the actual work to the app. Again, this may seem superfluous for such a simple app, but it’s a useful habit to get into.

Now, in the SEAtomShakerApp.hpp, we add the declaration of this shakeAtoms function:

and we are now ready to add the function definition.

Shaking atoms, for real

Our app is going to use several functionalities of the SAMSON SDK. Thus, we include the SAMSON header towards the beginning of the SEAtomShakerApp.cpp file:

 

We can now add the function definition at the end of the SEAtomShakerApp.cpp file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void SEAtomShakerApp::shakeAtoms() {
 
	SBNodeIndexer nodeIndexer;
	SAMSON::getActiveDocument()->getNodes(nodeIndexer, SBNode::IsType(SBNode::Atom));
 
	SB_FOR(SBNode* node, nodeIndexer) {
 
		SBAtom* atom = static_cast<SBAtom*>(node);
		SBPosition3 position = atom->getPosition();
 
		position.v[0] += SBQuantity::angstrom(1.0);
		atom->setPosition(position);
 
	}
 
}

Lines 3 and 4 collect all atoms in the active document, and store pointers to them in a node indexer. On line 4, SBNode::IsType(SBNode::Atom) is a predicate (i.e. an object that contains a function returning true or false when it is applied to a node). When passed to the getNodes function, it ensures that only atoms are added to the node indexer.

Then, the for loop (with the SB_FOR macro) goes over all indexed atoms to translate them by one angstrom in the x direction. Two things should be emphasized:

  • All nodes in the document derive (directly or indirectly) from the SBNode class. The node indexer stores pointers to nodes, and the type of the node variable is thus SBNode*. In order to use functionalities of atoms (such as the getPosition and setPosition function), we need to cast node to a pointer to a SBAtom object. This is done in line 8.
  • All physical quantities (lengths, energies, etc.) are strongly typed in SAMSON: they are not mere floating-point values, but have associated units . We thus need to specify the unit of the displacement on line 11.

 

The unit mechanism integrated in SAMSON ensures that developers from different backgrounds may work with units relevant to them, while ensuring physical correctness and preserving integration between SAMSON Elements of various origins. For a test, try to replace angstrom(1.0) by e.g. electronVolt(1.0), and the code will not compile, whereas picometer(100.0) will compile and give the same result as angstrom(1.0).

We may now compile and run SAMSON, and the app translates atoms:

 

Finally, in order to shake atoms in random directions, we include the SBRandom.hpp header that provides random number generators in the beginning of the SEAtomShakerApp.cpp file:

#include "SEAtomShakerApp.hpp"
#include "SEAtomShakerAppGUI.hpp"
#include "SAMSON.hpp"
#include "SBRandom.hpp"

and we modify the shakeAtoms function to use a random generator to perturb atoms positions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void SEAtomShakerApp::shakeAtoms() {
 
	SBNodeIndexer nodeIndexer;
	SAMSON::getActiveDocument()->getNodes(nodeIndexer, SBNode::IsType(SBNode::Atom));
 
	static SBRandom randomGenerator;
 
	SB_FOR(SBNode* node, nodeIndexer) {
 
		SBAtom* atom = static_cast<SBAtom*>(node);
		SBPosition3 position = atom->getPosition();
 
		position.v[0] += SBQuantity::angstrom(randomGenerator.randDouble1() - 0.5);
		position.v[1] += SBQuantity::angstrom(randomGenerator.randDouble1() - 0.5);
		position.v[2] += SBQuantity::angstrom(randomGenerator.randDouble1() - 0.5);
 
		atom->setPosition(position);
 
	}
 
}

Note the use of static in line 6, to force the random generator to be initialized only once (else, the same random perturbation would be applied each time we shake atoms).

After recompiling, the app now randomly shakes atoms:

 

Making it safe

What if users shake too much and want to undo the perturbation? In SAMSON, many functions are undoable, and we just need to turn on and off the holding mechanism which stores incremental modifications to the document:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void SEAtomShakerApp::shakeAtoms() {
 
	SBNodeIndexer nodeIndexer;
	SAMSON::getActiveDocument()->getNodes(nodeIndexer, SBNode::IsType(SBNode::Atom));
 
	static SBRandom randomGenerator;
 
	SAMSON::beginHolding("Shake atoms");
 
	SB_FOR(SBNode* node, nodeIndexer) {
 
		SBAtom* atom = static_cast<SBAtom*>(node);
		SBPosition3 position = atom->getPosition();
 
		position.v[0] += SBQuantity::angstrom(randomGenerator.randDouble1() - 0.5);
		position.v[1] += SBQuantity::angstrom(randomGenerator.randDouble1() - 0.5);
		position.v[2] += SBQuantity::angstrom(randomGenerator.randDouble1() - 0.5);
 
		atom->setPosition(position);
 
	}
 
	SAMSON::endHolding();
 
}

Line 8 turns the holding mechanism on. In the history, the user will see a “Shake atoms” command that can be undone. Line 23 turns the holding mechanism off. If the user undoes the command, all undoable functions called between beginHolding and endHolding (in this case, the setPosition calls) will be undone:

 

A select few

The user might want to perturb the positions of selected atoms only. To achieve this, we simply form a more complex predicate when we collect atoms:

void SEAtomShakerApp::shakeAtoms() {
	SAMSON::getActiveDocument()->getNodes(nodeIndexer, SBNode::IsType(SBNode::Atom) && SBNode::IsSelected());

This gives more control to the user:

 

Au revoir

Congratulations! You coded a SAMSON app that shakes selected atoms when the user presses a push button, and gives users the possibility to undo and redo their actions. You organized your code to cleanly separate the user interface from the core functionality of the app, and you touched interface design and widgets, signals and slots, nodes and node indexers, predicates, units, random generators, the holding mechanism, and selections. Good job!

Comments are closed