Web Analytics Made Easy - Statcounter
Skip to content

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

SB_CLASS_DESCRIPTION("DevTutorial: Atom pusher");

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.

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

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

Atom pusher editor