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
The source code for an Extension created in this tutorial can be found at SAMSON-Developer-Tutorials.
AtomPusherGenerating 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):
- launch SAMSON and run the SAMSON Extension Generator;
- specify the path to a folder where you want to develop your SAMSON Extensions;
- name the SAMSON Extension as AtomPusher and add some description;
- add an Editor class (called
SEAtomPusherEditor
); - generate the SAMSON Extension.
The SAMSON Extension Generator generates an editor class (derived from the SBGEditor class) and a property widget class for the editor.
AtomPusherDescription Setting the description of the Extension#
Open the header file of the editor descriptor (file: SEAtomPusherEditorDescriptor.hpp) and modify the class description:
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:
Add the getDescription
function in the editor class (file: SEAtomPusherEditor.hpp)
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:
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.
AtomPusherEditorInterace 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.
AtomPusherDisplaying 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
};
And we initialize them in the constructor of the editor class:
SEAtomPusherEditor::SEAtomPusherEditor() {
// ...
spherePosition = SBPosition3();
sphereRadius = SBQuantity::angstrom(1.0);
sphereIsActive = false;
SAMSON::requestViewportUpdate();
}
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) {
if (renderingPass == SBNode::RenderingPass::OpaqueGeometry ||
renderingPass == SBNode::RenderingPass::ShadowingGeometry) {
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);
}
}
}
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 Van der Waals visual model tutorial 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());
SAMSON::requestViewportUpdate();
}
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;
SAMSON::requestViewportUpdate();
}
}
void SEAtomPusherEditor::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::MouseButton::LeftButton) {
sphereIsActive = false;
SAMSON::requestViewportUpdate();
}
}
Add a function in the editor class (file: SEAtomPusherEditor.hpp) to push atoms
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;
SAMSON::getActiveDocument()->getNodes(nodeIndexer, SBNode::Atom);
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);
}
}
}
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 == nullptr)
spherePosition = SAMSON::getWorldPositionFromViewportPosition(event->pos());
else
spherePosition = SAMSON::getWorldPositionFromViewportPosition(event->pos(), nodePosition);
SAMSON::requestViewportUpdate();
pushAtoms();
}
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);
SAMSON::requestViewportUpdate();
pushAtoms();
}
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.