Atom Manipulation

Hi,

Got back from holiday early and spent some time coding yesterday. I saw
Geoff’s atom manipulation stuff in the select/rotate tool but wanted to see
if I could make a tool that manipulated atoms in the same way that the
navigate tool manipulates the view.

Right now left button translates the atom in x and y and the middle button
zooms the atom into and out of the display. One thing I thought may feel more
natural than the current zoom behaviour is if I computed a vector from the
camera centre through the atom center used that instead of the camera z axis.
With groups you would just use the group centre instead of the atom centre.

After talking with Benoit I have refined it a little but it is still only
working with single atoms under the mouse. With Geoff’s additions to glwidget
it will not take much to extend it though. The thought occurred to me whether
it was better to have these actions as a subsidiary function of a tool or as
the primary function of a tool, i.e. in the select/rotate tool or in its own
tool file.

Either way the actions in this patch and the new actions Geoff has added to
the select/rotate tool should be merged into the same tool at some point
really soon. I think clicking on a tool and being in manipulation mode would
feel most natural but I can also see how selecting atoms/residues and then
manipulating them also goes together very well.

There is also the other question that has been raised about keeping the
movements consistent across tools, i.e. navigate and selectrotate right now.
Another thing in my mind was adding some shortcuts, I would like to be able
to switch between tools with a few key strokes. Is this a good idea?

I see a possible workflow where I draw a molecule (draw tool), manipulate the
view (navigate tool), adjust some atoms positions (manipulate tool), draw
another group onto the molecule (draw tool), select a portion of the molecule
(selectrotate) and then move that group to where I want it (manipulate tool).
After that I would probably want to optimise the structure.

That is a lot of switching and once you use avo a fair bit shortcuts would be
extremely useful with a few tools specialising in specific areas. You guys
may have different ideas though and so I would appreciate any of your
thoughts on how this may be different.

Thanks,

Marcus

PS I was blown away by the number of changes in the last few days - need to
save some stuff for my Google Summer of Code project :wink: )

On Fri, 11 May 2007, Marcus D. Hanwell wrote:

Another thing in my mind was adding some shortcuts, I would like to be able
to switch between tools with a few key strokes. Is this a good idea?

Great idea! I think this deserves pure keystrokes, i.e. no modifier. Like
F5, F6, F7, etc.

I don’t have a firm opinion on whether manipulatetool should exist as a
separate tool or live inside the selecttool. But yes, as I told you by
private mail, I definitely think a merger with Geoff’s work is in order.
You do translation movement well, while Geoff does rotation and
manipulation of whole selections. Together that could give a very good
manipulatetool.

As so the uniformization of mouse buttons across tools, may I suggest that
we align on what’s currently done in navigatetool:
left-button translates as in a PDF viewer
mid-button zooms, which is logical because the wheel has to zoom anyway
right-button rotates because that’s the only button left and we need a
button for rotation anyway.

Here’s another idea: in case you and Geoff decide you want to go on with
having a single tool for both selection and manipulation, I suggest to
swap these two sub-tools: the plain mouse action would be manipulate (as
in your manipulatetool, Marcus) and the selection would be achieved with
the Shift modifier key. The rationale for that is that Shift+mouse is
already widely used for selection in most GUI software since ages.

Cheers,
Benoit

Let me quote myself :slight_smile:

On Fri, 11 May 2007, Benoit Jacob wrote:

Here’s another idea: in case you and Geoff decide you want to go on with
having a single tool for both selection and manipulation, I suggest to
swap these two sub-tools: the plain mouse action would be manipulate (as
in your manipulatetool, Marcus) and the selection would be achieved with
the Shift modifier key. The rationale for that is that Shift+mouse is
already widely used for selection in most GUI software since ages.

I just a big idea: selection should always be available, regardless of the
current active tool, through the Shift modifier key.

In the GUI, selection wouldn’t be exposed in the Tools dock.

The GLWidget’s mouseMove method would first test whether Shift is pressed.
If yes, the mouseMove event would be passed to the Select-tool. Otherwise,
the mouseMove event would be passed to the currently active tool.

This would

  1. greatly reduce the need for the user to frequently switch tools
  2. solve our question whether select and manipulate should coexist in the
    same tool. The answer would be, that we separate them.

Cheers,
Benoit

On May 11, 2007, at 4:16 AM, Marcus D. Hanwell wrote:

There is also the other question that has been raised about keeping
the
movements consistent across tools, i.e. navigate and selectrotate
right now.
Another thing in my mind was adding some shortcuts, I would like to
be able
to switch between tools with a few key strokes. Is this a good idea?

I do think we should let people switch between tools with shortcuts.
Also, when I was working on cut/copy/paste, I realized that we also
need some way in the code to do that switch.

For example, let’s say we paste in a new molecule, or have an “insert
from library” extension. Along the way, we should ignore the previous
selected atoms, select the new ones, and switch to let the user
manipulate them. Of course this flies in the face of modularity,
since the code assumes a certain plugin is actually loaded.

PS I was blown away by the number of changes in the last few days -
need to
save some stuff for my Google Summer of Code project :wink: )

Oh, don’t worry. If you want to hit the ground running, you can
always figure out what surface/mesh library we should use. (I suspect
this would be less work than writing your own and handling isosurfaces.)

Cheers,
-Geoff

On May 11, 2007, at 4:45 AM, Benoit Jacob wrote:

I just a big idea: selection should always be available, regardless
of the
current active tool, through the Shift modifier key.

How does the “rubber band” rectangle work?

GIMP and Photoshop have separate “selection” tools, and no one minds.
Indeed, many graphics editors have this. It’s also not that uncommon
to have a separate selection rectangle and manipulation tool.

But we should keep talking – this has been a productive set of ideas.

Cheers,
-Geoff

On Fri, 11 May 2007, Geoffrey Hutchison wrote:

How does the “rubber band” rectangle work?

Well, you hold down the Shift key, left-click, drag the mouse, and it
shows the rubber band rectangle. I don’t see the problem; am I missing
something?

GIMP and Photoshop have separate “selection” tools, and no one minds. Indeed,
many graphics editors have this. It’s also not that uncommon to have a
separate selection rectangle and manipulation tool.

But we should keep talking – this has been a productive set of ideas.

Sure – I was just suggesting an idea that had popped in my head. It’s not
important to me that we do it this way :slight_smile:

Cheers,
Benoit

On shortcuts: sounds good. eventually we can setup some “switch to X
tool” bindings.

On uniform manipulation: this is a tough one, i personally think i would
use rotation more than i would move, but again we are an editor, it
makes sense to me that move might in fact be more popular. Let the
chemists decide, Geoff…

On selection: I believe that you may be right that holding shift should
always be selection. I’m also thinking there might be times when a tool
wants shift for something else… maybe not. For now i’m cool with
overwritting that one key combo.

All good stuff, ideas are flying around. Code freeze after tonight so
lets not get too nuts with changing stuff.

Out

Donald

(Fri, May 11, 2007 at 03:54:13PM +0200) Benoit Jacob jacob@math.jussieu.fr:

On Fri, 11 May 2007, Geoffrey Hutchison wrote:

How does the “rubber band” rectangle work?

Well, you hold down the Shift key, left-click, drag the mouse, and it
shows the rubber band rectangle. I don’t see the problem; am I missing
something?

GIMP and Photoshop have separate “selection” tools, and no one minds. Indeed,
many graphics editors have this. It’s also not that uncommon to have a
separate selection rectangle and manipulation tool.

But we should keep talking – this has been a productive set of ideas.

Sure – I was just suggesting an idea that had popped in my head. It’s not
important to me that we do it this way :slight_smile:

Cheers,
Benoit


This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/


Avogadro-devel mailing list
Avogadro-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/avogadro-devel

Cryos,

Just go ahead and add the engine if you want. You dont’ need to send
patches. It’s not going to get in the way and if it gets removed it’s
no biggy. Engines / Tools are kinda brainstorming directories, if there
are some junk tools it’s not a bad thing; they will get naturally
cleaned up as they evolve an others replace them.


Donald

(Fri, May 11, 2007 at 09:16:19AM +0100) “Marcus D. Hanwell” marcus@cryos.org:

Hi,

Got back from holiday early and spent some time coding yesterday. I saw
Geoff’s atom manipulation stuff in the select/rotate tool but wanted to see
if I could make a tool that manipulated atoms in the same way that the
navigate tool manipulates the view.

Right now left button translates the atom in x and y and the middle button
zooms the atom into and out of the display. One thing I thought may feel more
natural than the current zoom behaviour is if I computed a vector from the
camera centre through the atom center used that instead of the camera z axis.
With groups you would just use the group centre instead of the atom centre.

After talking with Benoit I have refined it a little but it is still only
working with single atoms under the mouse. With Geoff’s additions to glwidget
it will not take much to extend it though. The thought occurred to me whether
it was better to have these actions as a subsidiary function of a tool or as
the primary function of a tool, i.e. in the select/rotate tool or in its own
tool file.

Either way the actions in this patch and the new actions Geoff has added to
the select/rotate tool should be merged into the same tool at some point
really soon. I think clicking on a tool and being in manipulation mode would
feel most natural but I can also see how selecting atoms/residues and then
manipulating them also goes together very well.

There is also the other question that has been raised about keeping the
movements consistent across tools, i.e. navigate and selectrotate right now.
Another thing in my mind was adding some shortcuts, I would like to be able
to switch between tools with a few key strokes. Is this a good idea?

I see a possible workflow where I draw a molecule (draw tool), manipulate the
view (navigate tool), adjust some atoms positions (manipulate tool), draw
another group onto the molecule (draw tool), select a portion of the molecule
(selectrotate) and then move that group to where I want it (manipulate tool).
After that I would probably want to optimise the structure.

That is a lot of switching and once you use avo a fair bit shortcuts would be
extremely useful with a few tools specialising in specific areas. You guys
may have different ideas though and so I would appreciate any of your
thoughts on how this may be different.

Thanks,

Marcus

PS I was blown away by the number of changes in the last few days - need to
save some stuff for my Google Summer of Code project :wink: )

Index: libavogadro/src/tools/manipulate.qrc

— libavogadro/src/tools/manipulate.qrc (revision 0)
+++ libavogadro/src/tools/manipulate.qrc (revision 0)
@@ -0,0 +1,5 @@
+

  •    <file>navigate.png</file>
    

+
\ No newline at end of file
Index: libavogadro/src/tools/manipulatetool.h

— libavogadro/src/tools/manipulatetool.h (revision 0)
+++ libavogadro/src/tools/manipulatetool.h (revision 0)
@@ -0,0 +1,111 @@
+/**********************************************************************

  • ManipulateTool - Manipulation Tool for Avogadro
  • Copyright © 2007 by Marcus D. Hanwell
  • Copyright © 2007 by Benoit Jacob
  • This file is part of the Avogadro molecular editor project.
  • For more information, see http://avogadro.sourceforge.net/
  • Some code is based on Open Babel
  • For more information, see http://openbabel.sourceforge.net/
  • This program is free software; you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation version 2 of the License.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • ***********************************************************************/

+#ifndef __MANIPULATETOOL_H
+#define __MANIPULATETOOL_H
+
+#include <avogadro/glwidget.h>
+#include <avogadro/tool.h>
+
+#include <openbabel/mol.h>
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Avogadro {
+

  • /**
    • @class ManipulateTool
    • @brief Manipulate the position of atoms
    • @author Marcus D. Hanwell
    • This tool enables the manipulation of the position of
    • the selected atoms.
  • */
  • class ManipulateTool : public Tool
  • {
  • Q_OBJECT
  • public:
  •  //! Constructor
    
  •  ManipulateTool(QObject *parent = 0);
    
  •  //! Deconstructor
    
  •  virtual ~ManipulateTool();
    
  •  //! \name Description methods
    
  •  //@{
    
  •  //! Tool Name (ie Draw)
    
  •  virtual QString name() const { return(tr("Manipulate")); }
    
  •  //! Tool Description (ie. Draws atoms and bonds)
    
  •  virtual QString description() const { return(tr("Manipulation Tool")); }
    
  •  //@}
    
  •  //! \name Tool Methods
    
  •  //@{
    
  •  //! \brief Callback methods for ui.actions on the canvas.
    
  •  /*!
    
  •  */
    
  •  virtual QUndoCommand* mousePress(GLWidget *widget, const QMouseEvent *event);
    
  •  virtual QUndoCommand* mouseRelease(GLWidget *widget, const QMouseEvent *event);
    
  •  virtual QUndoCommand* mouseMove(GLWidget *widget, const QMouseEvent *event);
    
  •  virtual QUndoCommand* wheel(GLWidget *widget, const QWheelEvent *event);
    
  •  virtual int usefulness() const;
    
  •  virtual bool paint(GLWidget *widget);
    
  • protected:
  •  GLWidget *          m_glwidget;
    
  •  bool                m_leftButtonPressed;  // rotation
    
  •  bool                m_rightButtonPressed; // translation
    
  •  bool                m_midButtonPressed;   // scale / zoom
    
  •  Atom *              m_clickedAtom;
    
  •  //! Temporary var for adding selection box
    
  •  GLuint              m_selectionDL;
    
  •  QPoint              m_lastDraggingPosition;
    
  •  void drawSphere(GLWidget *widget,  const Eigen::Vector3d &center, double radius, float alpha);
    
  •  void computeClickedAtom(const QPoint& p);
    
  •  void zoom( const Eigen::Vector3d &goal, double delta ) const;
    
  •  void translate( const Eigen::Vector3d &what, const QPoint &from, const QPoint &to ) const;
    
  •  void rotate( const Eigen::Vector3d &center, double deltaX, double deltaY ) const;
    
  •  void tilt( const Eigen::Vector3d &center, double delta ) const;
    
  • };
  • class ManipulateToolFactory : public QObject, public ToolFactory
  • {
  •  Q_OBJECT;
    
  •  Q_INTERFACES(Avogadro::ToolFactory);
    
  •  public:
    
  •    Tool *createInstance(QObject *parent = 0) { return new ManipulateTool(); }
    
  • };

+} // end namespace Avogadro
+
+#endif
Index: libavogadro/src/tools/CMakeLists.txt

— libavogadro/src/tools/CMakeLists.txt (revision 357)
+++ libavogadro/src/tools/CMakeLists.txt (working copy)
@@ -65,3 +65,15 @@
)
INSTALL(TARGETS navigatetool DESTINATION ${DESTINATION_DIR})

+### manipulatetool
+set(manipulatetool_SRCS manipulatetool.cpp)
+qt4_add_resources(manipulatetool_RC_SRCS manipulate.qrc)
+qt4_automoc({manipulatetool_SRCS}) +ADD_LIBRARY(manipulatetool SHARED {manipulatetool_SRCS} ${manipulatetool_RC_SRCS})
+TARGET_LINK_LIBRARIES(manipulatetool

  • ${OPENBABEL2_LIBRARIES}
  • ${QT_LIBRARIES}
  • ${OPENGL_LIBRARIES}
  • avogadro-lib
    +)
    +INSTALL(TARGETS manipulatetool DESTINATION ${DESTINATION_DIR})
    Index: libavogadro/src/tools/manipulatetool.cpp
    ===================================================================
    — libavogadro/src/tools/manipulatetool.cpp (revision 0)
    +++ libavogadro/src/tools/manipulatetool.cpp (revision 0)
    @@ -0,0 +1,243 @@
    +/**********************************************************************
  • ManipulateTool - Manipulation Tool for Avogadro
  • Copyright © 2007 by Marcus D. Hanwell
  • Copyright © 2007 by Benoit Jacob
  • This file is part of the Avogadro molecular editor project.
  • For more information, see http://avogadro.sourceforge.net/
  • Some code is based on Open Babel
  • For more information, see http://openbabel.sourceforge.net/
  • This program is free software; you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation version 2 of the License.
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details.
  • ***********************************************************************/

+#include “manipulatetool.h”
+#include <avogadro/primitive.h>
+#include <avogadro/color.h>
+#include <avogadro/glwidget.h>
+#include <avogadro/camera.h>
+
+#include <openbabel/obiter.h>
+#include <openbabel/mol.h>
+
+#include
+
+using namespace std;
+using namespace OpenBabel;
+using namespace Avogadro;
+using namespace Eigen;
+
+ManipulateTool::ManipulateTool(QObject *parent) : Tool(parent), m_clickedAtom(0), m_leftButtonPressed(false), m_midButtonPressed(false), m_rightButtonPressed(false)
+{

  • QAction *action = activateAction();
  • action->setIcon(QIcon(QString::fromUtf8(":/navigate/navigate.png")));
  • action->setToolTip(tr(“Manipulation Tool\n\n”
  •    "Left Mouse:   Click and drag to move atoms and bonds\n"
    
  •    "Middle Mouse: Click and drag to move atoms further away or closer\n"
    
  •    "Right Mouse:  Click and drag to rotate atoms and bonds"));
    

+}
+
+ManipulateTool::~ManipulateTool()
+{
+}
+
+int ManipulateTool::usefulness() const
+{

  • return 600000;
    +}

+void ManipulateTool::computeClickedAtom(const QPoint& p)
+{

  • QList hits;
  • m_clickedAtom = 0;
  • // Perform a OpenGL selection and retrieve the list of hits.
  • hits = m_glwidget->hits(p.x()-SEL_BOX_HALF_SIZE,
  •  p.y()-SEL_BOX_HALF_SIZE,
    
  •  SEL_BOX_SIZE, SEL_BOX_SIZE);
    
  • // Find the first atom (if any) in hits - this will be the closest
  • foreach( GLHit hit, hits )
  • {
  • if(hit.type() == Primitive::AtomType)
  • {
  •  m_clickedAtom = static_cast<Atom *>( m_glwidget->molecule()->GetAtom(hit.name()) );
    
  •  return;
    
  • }
  • }
    +}

+void ManipulateTool::zoom( const Eigen::Vector3d &goal, double delta ) const
+{

  • Vector3d transformedGoal = m_glwidget->camera()->modelview() * goal;
  • double distanceToGoal = transformedGoal.norm();
  • double t = ZOOM_SPEED * delta;
  • const double minDistanceToGoal = 2.0 * CAMERA_NEAR_DISTANCE;
  • double u = minDistanceToGoal / distanceToGoal - 1.0;
  • if( fabs(t) > fabs(u) ) {
  • t = u;
  • }
  • MatrixP3d atomTranslation;
  • atomTranslation.loadTranslation(m_glwidget->camera()->backTransformedZAxis() * t);
  • if (m_clickedAtom)
  • m_clickedAtom->setPos(atomTranslation * m_clickedAtom->pos());
    +}

+void ManipulateTool::translate( const Eigen::Vector3d &what, const QPoint &from, const QPoint &to ) const
+{

  • Vector3d fromPos = m_glwidget->camera()->unProject(from, what);
  • Vector3d toPos = m_glwidget->camera()->unProject(to, what);
  • MatrixP3d atomTranslation;
  • atomTranslation.loadTranslation(toPos - fromPos);
  • if (m_clickedAtom)
  • m_clickedAtom->setPos(atomTranslation * m_clickedAtom->pos());
    +}

+void ManipulateTool::rotate( const Eigen::Vector3d &center, double deltaX, double deltaY ) const
+{
+
+}
+
+void ManipulateTool::tilt( const Eigen::Vector3d &center, double delta ) const
+{
+
+}
+
+QUndoCommand* ManipulateTool::mousePress(GLWidget *widget, const QMouseEvent *event)
+{

  • m_glwidget = widget;
  • m_lastDraggingPosition = event->pos();
  • m_leftButtonPressed = ( event->buttons() & Qt::LeftButton );
  • m_midButtonPressed = ( event->buttons() & Qt::MidButton );
  • m_rightButtonPressed = ( event->buttons() & Qt::RightButton );
  • computeClickedAtom(event->pos());
  • widget->update();
  • return 0;
    +}

+QUndoCommand* ManipulateTool::mouseRelease(GLWidget *widget, const QMouseEvent *event)
+{

  • m_glwidget = widget;
  • m_leftButtonPressed = false;
  • m_midButtonPressed = false;
  • m_rightButtonPressed = false;
  • m_clickedAtom = 0;
  • widget->update();
  • return 0;
    +}

+QUndoCommand* ManipulateTool::mouseMove(GLWidget *widget, const QMouseEvent *event)
+{

  • m_glwidget = widget;
  • if(!m_glwidget->molecule()) {
  • return 0;
  • }
  • QPoint deltaDragging = event->pos() - m_lastDraggingPosition;
  • // Manipulation can be performed in two ways - centred on an individual atom
  • if( m_clickedAtom )
  • {
  • if ( event->buttons() & Qt::LeftButton )
  • {
  •  // translate the molecule following mouse movement
    
  •  translate( m_clickedAtom->pos(), m_lastDraggingPosition, event->pos() );
    
  • }
  • else if ( event->buttons() & Qt::MidButton )
  • {
  •  // Perform the rotation
    

+// tilt( m_clickedAtom->pos(), deltaDragging.x() );
+

  •  // Perform the zoom toward clicked atom
    
  •  zoom( m_clickedAtom->pos(), deltaDragging.y() );
    
  • }
  • else if ( event->buttons() & Qt::RightButton )
  • {
  •  // Atom centred rotation 
    
  •  rotate( m_clickedAtom->pos(), deltaDragging.x(), deltaDragging.y() );
    
  • }
  • }
  • else // Nothing clicked on
  • {
  • if( event->buttons() & Qt::RightButton )
  • {
  •  // rotation around the center of the molecule
    
  •  rotate( m_glwidget->center(), deltaDragging.x(), deltaDragging.y() );
    
  • }
  • else if ( event->buttons() & Qt::MidButton )
  • {
  •  // Perform the rotation
    
  •  tilt( m_glwidget->center(), deltaDragging.x() );
    
  •  // Perform the zoom toward molecule center
    
  •  zoom( m_glwidget->center(), deltaDragging.y() );
    
  • }
  • else if ( event->buttons() & Qt::LeftButton )
  • {
  •  // translate the molecule following mouse movement
    
  •  translate( m_glwidget->center(), m_lastDraggingPosition, event->pos() );
    
  • }
  • }
  • m_lastDraggingPosition = event->pos();
  • m_glwidget->update();
  • return 0;
    +}

+QUndoCommand* ManipulateTool::wheel(GLWidget *widget, const QWheelEvent *event )
+{

  • return 0;
    +}

+bool ManipulateTool::paint(GLWidget *widget)
+{

  • if(m_leftButtonPressed || m_midButtonPressed || m_rightButtonPressed) {
  • if(m_clickedAtom) {
  •  double renderRadius = 0.0;
    
  •  foreach(Engine *engine, widget->engines())
    
  •  {
    
  •    if(engine->isEnabled())
    
  •    {
    
  •      double engineRadius = engine->radius(m_clickedAtom);
    
  •      if(engineRadius > renderRadius) {
    
  •        renderRadius = engineRadius;
    
  •      }
    
  •    }
    
  •  }
    
  •  renderRadius += 0.10;
    
  •  drawSphere(widget, m_clickedAtom->GetVector().AsArray(), renderRadius, 0.7);
    
  • }
  • }
  • return true;
    +}

+void ManipulateTool::drawSphere(GLWidget *widget, const Eigen::Vector3d &position, double radius, float alpha )
+{

  • Color( 1.0, 0.3, 0.3, alpha ).applyAsMaterials();
  • glEnable( GL_BLEND );
  • widget->painter()->drawSphere(position, radius);
  • glDisable( GL_BLEND );
    +}

+#include “manipulatetool.moc”
+
+Q_EXPORT_PLUGIN2(manipulatetool, ManipulateToolFactory)


This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/


Avogadro-devel mailing list
Avogadro-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/avogadro-devel