Loading...
Searching...
No Matches
Programming new editors: Atom pusher

In this tutorial, you will learn how to program an Editor that pushes atoms in the viewport. Our editor will be a sphere, that when active pushes atoms.

See also
Editors

The source code for an Extension created in this tutorial can be found at https://github.com/1A-OneAngstrom/SAMSON-Developer-Tutorials.

Generating an Extension

We will start by creating a new SAMSON Extension called AtomPusher thanks to the SAMSON Extension generator (please, refer to the SAMSON Extension generator tutorial for a reminder on how to use it):

The SAMSON Extension generator generates an editor class (derived from the SBGEditor class) and a property widget class for the editor.

Setting the description of the Extension

Open the header file of the editor descriptor (file: SEAtomPusherEditorDescriptor.hpp) and modify the class description:

SB_CLASS_DESCRIPTION("DevTutorial: Atom pusher");
#define SB_CLASS_DESCRIPTION(CLASS_DESCRIPTION)
Declares the description of the class.
Definition: SBCClassProxy.hpp:202

Modify the getName function for both the editor class (file: SEAtomPusherEditor.cpp) and the editor GUI class (file: SEAtomPusherEditorGUI.cpp) to have a user-friendly description of the editor inside SAMSON:

return "DevTutorial: Atom pusher";

Add the getDescription function in the editor class (file: SEAtomPusherEditor.hpp)

virtual QString getDescription() const; ///< Returns the menu item text

And implement it as follows to have a user-friendly description of the editor in the menu:

QString SEAtomPusherEditor::getDescription() const {
return QObject::tr("DevTutorial: Atom pusher");
}

Modify the getToolTip function in the editor class (file: SEAtomPusherEditor.cpp) to have a user-friendly description of what the editor does:

QString SEAtomPusherEditor::getToolTip() const {
return QObject::tr("Pushes atoms when pressed");
}

Now, try building the SAMSON Extension and when you launch SAMSON you should be able to find it in Home > Apps. For now, it has no interface and does nothing, but we will now add functionality to it.

Interface of an editor

The SAMSON Extension generator provides a GUI class (a property widget) for an editor which can be invoked by double-clicking on the editor's icon in the toolbar.

If your editor does not require GUI, you can disable it by simply commenting the creation and destruction of the property widget in the constructor and in the destructor of the editor class, respectively.

Displaying an editor

Since our editor is a sphere which can be in two states (active or not), we add the following variables in the editor class (file: SEAtomPusherEditor.hpp):

class SEAtomPusherEditor : public SBGEditor {
// ...
public:
SBPosition3 spherePosition; // position of the sphere
SBQuantity::length sphereRadius; // radius of the sphere
bool sphereIsActive; // true of sphere is active
};
This template class defines physical quantity types.
Definition: SBDQuantityType.hpp:43
This class is the base class for Editor.
Definition: SBGEditor.hpp:30

And we initialize them in the constructor of the editor class:

SEAtomPusherEditor::SEAtomPusherEditor() {
// ...
spherePosition = SBPosition3();
sphereRadius = SBQuantity::angstrom(1.0);
sphereIsActive = false;
}
SBDTypePhysicalVector3< SBQuantity::position > SBPosition3
Three-dimensional vector with components in units of length.
Definition: SBDTypePhysicalVector3.hpp:840
static void requestViewportUpdate()
Requests a viewport update.
Definition: SAMSON.cpp:1402
SBDQuantityType< SBDQuantityUnitType< SBUnitSystemSI, -10, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 > > angstrom
The angstrom type.
Definition: SBDQuantity.hpp:81

Let's now create a visual representation of our editor, the sphere. For that we implement the display function in the editor class (file: SEAtomPusherEditor.cpp):

void SEAtomPusherEditor::display(SBNode::RenderingPass renderingPass) {
float positionData[3];
float radiusData[1];
positionData[0] = static_cast<float>(spherePosition[0].getValue());
positionData[1] = static_cast<float>(spherePosition[1].getValue());
positionData[2] = static_cast<float>(spherePosition[2].getValue());
radiusData[0] = static_cast<float>(sphereRadius.getValue());
if (renderingPass == SBNode::RenderingPass::OpaqueGeometry) {
float colorData[4];
unsigned int flagData[1];
if (sphereIsActive){
// a white sphere
colorData[0] = 1.0f;
colorData[1] = 1.0f;
colorData[2] = 1.0f;
colorData[3] = 1.0f;
}
else {
// a green sphere
colorData[0] = 0.0f;
colorData[1] = 1.0f;
colorData[2] = 0.0f;
colorData[3] = 1.0f;
}
flagData[0] = 0;
SAMSON::displaySpheres(1, positionData, radiusData, colorData, flagData);
}
else if (renderingPass == SBNode::RenderingPass::ShadowingGeometry) {
// display for shadows
SAMSON::displaySpheres(1, positionData, radiusData, nullptr, nullptr, true);
}
}
}
static void displaySpheres(unsigned int nSpheres, const float *positionData, const float *radiusData, const float *colorData, const unsigned int *flagData, bool shadowPassFlag=false, bool transparency=false, float opacity=1.0f)
Displays spheres.
Definition: SAMSON.cpp:3716
RenderingPass
The rendering pass.
Definition: SBDDataGraphNode.hpp:193
@ ShadowingGeometry
The pass where shadowing geometry is rendered.
@ OpaqueGeometry
The pass where opaque geometry is rendered.

Here, we fill in arrays for position of the sphere, its radius, and color, which we send to the SAMSON::displaySpheres function that will render the sphere in SAMSON's viewport. We also implement the shadow casting in the display function (see Casting shadows for more information).

In the display function we used the sphere's position, to know this position we need to implement the mouseMoveEvent function in the editor class (file: SEAtomPusherEditorGUI.cpp):

void SEAtomPusherEditor::mouseMoveEvent(QMouseEvent* event) {
spherePosition = SAMSON::getWorldPositionFromViewportPosition(event->pos());
}
static SBPosition3 getWorldPositionFromViewportPosition(double x, double y)
Returns the 3D position that corresponds to the viewport location (x,y)
Definition: SAMSON.cpp:1329

Here, we set the shpere's position from the cursor position in the viewport.

Now, try building the Extension and when you launch SAMSON you should be able to find it in the editor's menu on the left-side of the Viewport or find it using Find everything... search box at the top of SAMSON. For now, it has no editing functionality, but you should be able to see the editor's sphere in the SAMSON's viewport once select the editor and move the mouse in the viewport.

Pushing atoms

SAMSON redirects Qt events to the active editor (see the Editors section for more information). The editor class provides a number of possibilities to handle the GUI interactions:

  • mousePressEvent - handles mouse press event;
  • mouseReleaseEvent - handles mouse release event;
  • mouseMoveEvent - handles mouse move event;
  • mouseDoubleClickEvent - handles mouse double click event;
  • wheelEvent - handles wheel event;
  • keyPressEvent - handles key press event;
  • keyReleaseEvent - handles key release event.

Since our sphere will be pushing atoms only when it is active. We assume that our editor is active when the mouse button is pressed. For that, we implement mousePressEvent and mouseReleaseEvent functions as follows:

void SEAtomPusherEditor::mousePressEvent(QMouseEvent* event) {
if (event->button() == Qt::MouseButton::LeftButton) {
sphereIsActive = true;
}
}
void SEAtomPusherEditor::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::MouseButton::LeftButton) {
sphereIsActive = false;
}
}

Add a function in the editor class (file: SEAtomPusherEditor.hpp) to push atoms

void pushAtoms();

And implement it as follows:

void SEAtomPusherEditor::pushAtoms() {
// check if the sphere is in active state
if (!sphereIsActive) return;
// get an indexer of all atoms in the active document
SBNodeIndexer nodeIndexer;
SB_FOR(SBNode * node, nodeIndexer){
SBPointer<SBAtom> atom = static_cast<SBAtom*>(node);
if (!atom.isValid()) continue;
// get the atom's position
SBPosition3 atomPosition = atom->getPosition();
// compute the vector from the sphere's centers to the atom's center
SBPosition3 vectorFromSphereCenter = atomPosition - spherePosition;
// set the min distance between centers of the sphere and the atom by summing their radii
SBQuantity::length minDistance = sphereRadius + SAMSON::getAtomRadius();
// check if the sphere touches the atom
if (vectorFromSphereCenter.norm() < minDistance){
// push the atom in the direction from the sphere
vectorFromSphereCenter = vectorFromSphereCenter * (minDistance / vectorFromSphereCenter.norm());
atom->setPosition(spherePosition + vectorFromSphereCenter);
}
}
}
#define SB_FOR(VARIABLE, CONTAINER)
A macro to easily write for loops involving containers.
Definition: SBCContainerFor.hpp:83
static const SBQuantity::length & getAtomRadius()
Returns the radius of atoms in the default representation of structural models (when a constant radiu...
Definition: SAMSON.cpp:3938
static SBDDocument * getActiveDocument()
Returns a pointer to SAMSON's active document.
Definition: SAMSON.cpp:738
This class is the base class to describe a node in the data graph.
Definition: SBDDataGraphNode.hpp:33
@ Atom
Atom.
Definition: SBDDataGraphNode.hpp:67
This class describes a node indexer.
Definition: SBDDataGraphNodeIndexer.hpp:21
virtual void getNodes(SBNodeIndexer &nodeIndexer, SBNode::Type nodeType, bool selectedNodesOnly=false, const SBNodePredicate &visitPredicate=SBDDataGraphNode::All(), bool includeDependencies=false) const override
Collects nodes into nodeIndexer, based on a nodeType, a selection status and a visitPredicate,...
Definition: SBDDocumentFolder.cpp:804
Quantity norm() const
Returns the norm of this physical vector.
Definition: SBDTypePhysicalVector3.hpp:520
This class describes an atom in a structural model.
Definition: SBMStructuralModelNodeAtom.hpp:34

First, we check whether the sphere is active (the sphere pushes atoms only in the active state). We get a node indexer of all atoms in the active documents. Then we get the position of an atom and check whether it is touched by the sphere, if so we push it in the direction from the sphere.

Before, we implemented the mouseMoveEvent function in the editor class to update the sphere's position when the cursor moves. Let's now re-implement this function to handle the mouse movement for pushing atoms:

void SEAtomPusherEditor::mouseMoveEvent(QMouseEvent* event) {
SBPosition3 nodePosition;
SBNode* pickedNode = SAMSON::getNode(event->pos(), nodePosition);
if (pickedNode == NULL)
spherePosition = SAMSON::getWorldPositionFromViewportPosition(event->pos());
else
spherePosition = SAMSON::getWorldPositionFromViewportPosition(event->pos(), nodePosition);
pushAtoms();
}
static SBNode * getNode(int x, int y, const SBNodePredicate &selectionFilter=SBNode::All())
Returns the node at location (x,y) in the viewport according to the selectionFilter.
Definition: SAMSON.cpp:960

First, we update the sphere's position and then me invoke the pushAtoms function.

We also make it possible to change the sphere's radius through the wheel event by implementing the according function in the editor class:

void SEAtomPusherEditor::wheelEvent(QWheelEvent* event) {
int angle = event->angleDelta().y();
sphereRadius = sphereRadius * pow(1.002, angle);
// check for the minimal size of the sphere
if (sphereRadius < SBQuantity::angstrom(0.1)) sphereRadius = SBQuantity::angstrom(0.1);
pushAtoms();
}
SBDQuantityType< SBDQuantityUnitType< typename Unit::SystemType, Unit::scale1, p *Unit::exponent1, Unit::scale2, p *Unit::exponent2, Unit::scale3, p *Unit::exponent3, Unit::scale4, p *Unit::exponent4, Unit::scale5, p *Unit::exponent5, Unit::scale6, p *Unit::exponent6, Unit::scale7, p *Unit::exponent7 >, Value > pow(const SBDQuantityType< Unit, Value > &q)
Returns the p th power of physical quantity q.
Definition: SBDQuantityType.hpp:1008

Note, that by invoking the pushAtoms function in the end we also take into account that the sphere might be at the same time in the active state pushing atoms.

Now, you have a working editor for pushing atoms. Build the Extension, launch SAMSON, load or create any molecule, and choose the developed editor from the editor's menu on the left-side of the Viewport or find it using Find everything... search box at the top of SAMSON. Try moving the editor around, activating it, pushing atoms, and changing the sphere's radius.

An atom pusher editor