Commit 20549614 authored by Alexander Stukowski's avatar Alexander Stukowski

Generalized the Manual Selection modifier, now selecting bonds as well as particles

parent 0f1792cd
Pipeline #26102194 passed with stage
in 11 minutes and 16 seconds
......@@ -14,18 +14,18 @@
<imagedata fileref="images/ovito_screenshot.png" format="PNG" />
</imageobject></mediaobject></screenshot></informalfigure>
OVITO is a scientific data visualization and analysis software for
atomistic simulation models in materials science and related disciplines.
OVITO is freely available for
Windows, Linux, and macOS under an open-source license. It is being developed by <link xlink:href="http://scholar.google.com/citations?user=f8Tw3eEAAAAJ">Dr. Alexander
Stukowski</link> at the <link xlink:href="http://www.mawi.tu-darmstadt.de/mm/mm_mm_sw/index.en.jsp">
Materials Science Department</link> of Darmstadt University of Technology, Germany.
OVITO is a scientific data visualization and analysis software for
atomistic simulation models in materials science and related physics and chemistry disciplines.
OVITO is freely available for
Windows, Linux, and macOS under an open-source license. It is being developed by <link xlink:href="http://scholar.google.com/citations?user=f8Tw3eEAAAAJ">Dr. Alexander
Stukowski</link> at the <link xlink:href="http://www.mawi.tu-darmstadt.de/mm/mm_mm_sw/index.en.jsp">
Materials Science Department</link> of Darmstadt University of Technology, Germany.
</para>
<para>
The first version of OVITO was released in December 2009 and its initial feature set was described in
a <link xlink:href="http://stacks.iop.org/0965-0393/18/015012">journal article about OVITO</link> in 2010.
Today OVITO has more than active 100,000 users from the computational physics, materials science, and chemistry fields.
The first version of OVITO was released in 2009 and has been described in
a <link xlink:href="http://stacks.iop.org/0965-0393/18/015012">journal article</link>.
Today, OVITO has over 200,000 regular users from the computational physics, materials science and chemistry fields.
</para>
<simplesect>
......@@ -42,31 +42,31 @@
</listitem>
<listitem>
<para>Interactive display of large numbers of particles and bonds using hardware-accelerated OpenGL rendering.</para>
<para>Interactive visualization of a large numbers of particles and bonds using hardware-accelerated OpenGL rendering.</para>
</listitem>
<listitem>
<para>Support for <link linkend="howto.aspherical_particles">various particle shapes</link>: spheres, cubes, ellipsoids, cylinders, and more.</para>
<para>Support for <link linkend="howto.aspherical_particles">various particle shapes</link>: spheres, cubes, ellipsoids, cylinders and more.</para>
</listitem>
<listitem>
<para>An easy-to-use interface, a flexible software design, and a <link linkend="usage.particle_properties">data model</link> that supports an arbitrary number of particle properties.</para>
<para>An easy-to-use graphical interface and a <link linkend="usage.particle_properties">data model</link> that supports an arbitrary number of particle properties.</para>
</listitem>
<listitem>
<para>High-quality <link linkend="usage.rendering">image and movie rendering</link> for publication and a <link linkend="usage.animation">keyframe-based animation system</link>.</para>
<para>High-quality <link linkend="usage.rendering">image and movie rendering</link> capabilities and a <link linkend="usage.animation">keyframe-based animation system</link>.</para>
</listitem>
<listitem>
<para><link linkend="usage.scripting">A powerful Python-based scripting interface</link>
that allows one to automate analysis and visualization tasks
and <link linkend="particles.modifiers.python_script">extend OVITO</link> with new functions.</para>
</listitem>
that allows you to automate data analysis and visualization tasks
and to <link linkend="particles.modifiers.python_script">extend OVITO</link> in various ways.</para>
</listitem>
<listitem>
<para><link linkend="usage.modification_pipeline">Nondestructive data manipulation and analysis functions</link>
<para><link linkend="usage.modification_pipeline">Non-destructive data manipulation and analysis functions</link>
are arranged in a data flow pipeline by the user. Parameters can be changed at
any time and results will be immediately recalculated. Available <link linkend="particles.modifiers">modification functions</link> include:<itemizedlist>
any time and results will immediately be recalculated. Available <link linkend="particles.modifiers">modification functions</link> include:<itemizedlist>
<listitem>
<para><link linkend="particles.modifiers.expression_select">Dynamic particle selections based on Boolean expressions</link></para>
......@@ -165,7 +165,7 @@
</listitem>
<listitem>
<para>A modular software design, which allows to extend OVITO via C++ plugins</para>
<para>A modular software design, which allows extending OVITO via C++ plugins</para>
</listitem>
</itemizedlist>
......
......@@ -191,7 +191,7 @@
</row>
<row>
<entry><link linkend="particles.modifiers.manual_selection">Manual&#xA0;selection</link></entry>
<entry>Allows you to select particles with the mouse.</entry>
<entry>Lets you select individual particles or bonds with the mouse.</entry>
</row>
<row>
<entry><link linkend="particles.modifiers.invert_selection">Invert&#xA0;selection</link></entry>
......
......@@ -79,6 +79,10 @@ Modelling Simul. Mater. Sci. Eng. 20, 085007 (2012)</link></literallayout>
computationally expensive analysis. The CA file format is documented <link linkend="particles.modifiers.dislocation_analysis.fileformat">below</link>.
</para>
<para>
Note: The DXA produces dislocation lines and Burgers vectors that follow the left-hand start-finish (LH/SF) convention.
</para>
<simplesect>
<title>Parameters</title>
......
......@@ -15,17 +15,22 @@
<imagedata fileref="images/modifiers/manual_selection_panel.png" format="PNG" scale="50" />
</imageobject></mediaobject></screenshot></informalfigure>
This modifier lets the user manually select particles in the viewports using the mouse courser,
overwriting any previously defined particle selection.
This modifier lets the you manually select individual particles or bonds in the viewports using the mouse.
You can use it to restrict the action of subsequent modifiers in the pipeline to a selected
subset of elements.
</para>
<para>
Two input modes are available to select particles:
The <guibutton>Pick particles</guibutton> mode allows you to toggle the selection status of individual particles.
With the <guibutton>Fence selection</guibutton> mode, you can draw a closed fence around a group of particles in the viewports using the mouse.
All particles within the polygonal path will be selected. Hold down the <keycap>Ctrl</keycap>
key (<keycap>Command</keycap> on Mac) to add particles to an existing selection.
The <keycap>Alt</keycap> key can be used to remove particles from an existing selection.
Two selection tools are available:
The <guibutton>Pick</guibutton> mode lets you toggle the selection status of individual particles or bonds
by picking them with the mouse.
The <guibutton>Fence selection</guibutton> mode lets you draw a closed fence around a group of particles or bonds in the viewports.
All elements within the polygonal path will be selected. Hold down the <keycap>Ctrl</keycap>
key (<keycap>Command</keycap> on Mac) to add elements to the existing selection set.
The <keycap>Alt</keycap> key can be used to remove elements from an existing selection set.
<screenshot><mediaobject><imageobject>
<imagedata fileref="images/modifiers/manual_selection_fence_mode.png" format="PNG" scale="50" />
</imageobject></mediaobject></screenshot>
</para>
</section>
......@@ -53,21 +53,21 @@
<varlistentry><term>File containing multiple frames:</term><listitem>
<para>
OVITO will automatically detect when the imported file contains multiple frames and loads them as an animation sequence.
For some file types, such as XYZ and LAMMPS dump files, this mode is indicated by the <emphasis>Contains multiple timesteps</emphasis>
check box highlighted in the screenshot. Note that OVITO typically keeps only a single frame at a time in memory. Other frames are loaded from the source data
file only as needed, for example if you play back the animation or move the time slider.
OVITO automatically detects when the imported file contains more than one frame and loads them as an animation sequence.
For some file types, e.g. XYZ and LAMMPS dump, this is indicated by the <emphasis>Contains multiple timesteps</emphasis>
checkbox highlighted in the screenshot. Note that OVITO typically keeps only the data of a single frame in memory at a time.
Subsequent frames are loaded into memory only when needed, for example if you play back the animation or move the time slider.
</para>
</listitem></varlistentry>
<varlistentry><term>Separate files for topology and trajectories:</term><listitem>
<varlistentry><term>Separate topology and trajectory files:</term><listitem>
<para>
Some MD simulation codes work with separate topology and trajectory files. The topology file stores the static definition of
atoms, types, bonds, etc. while the trajectory file contains just the motion trajectories computed by the simulation.
In such a case, you should load the topology file first (e.g. a LAMMPS <emphasis>data</emphasis> file).
Some MD simulation codes work with separate topology and trajectory files. The topology file contains the static definition of
atoms, types, bonds, etc. while the trajectory file stores the motion trajectories and other time-dependent data computed by the simulation code.
In such a case, you should open the topology file first (e.g. a LAMMPS <emphasis>data</emphasis> file).
Then use the <link linkend="particles.modifiers.load_trajectory">Load trajectory</link> modifier to load the time-dependent atomic positions
from the separate trajectory file (e.g. a LAMMPS <emphasis>dump</emphasis> file). This modifier will
merge both pieces of information, the static topology and the trajectories, on the fly into a single time-dependent
on the fly merge both pieces of information -the static topology and the trajectory data- into a single time-dependent
dataset.
</para>
</listitem></varlistentry>
......@@ -127,18 +127,22 @@
<imagedata fileref="images/usage/importexport/pipeline_selector.png" format="PNG" scale="50" />
</imageobject></mediaobject></screenshot>
</informalfigure>
OVITO allows you to load several datasets and show them side by side in one picture,
and it allows you to load the same file more than once in order to visualize the data in several different ways for comparison.
To do this, simply invoke the <menuchoice><guimenu>File</guimenu><guimenuitem>Load File</guimenuitem></menuchoice> function from the menu repeatedly.
When OVITO asks you whether to replace the already loaded dataset or not, pick the "<emphasis>Add to scene</emphasis>" option.
All loaded datasets (more precisely, all data pipelines that are part of the scene) are listed in the drop-down box at the top
of the command panel (see screenshot). This drop-down box switches the pipeline that is currently shown in the
<link linkend="usage.modification_pipeline.pipeline_listbox">pipeline editor</link>.
OVITO allows you to load several datasets and visualize them side by side in one picture.
Or you can visualize the same input data in multiple ways, either side by side or superimposed on each other,
using several different data pipelines.
To do this, either use the <menuchoice><guimenu>Edit</guimenu><guimenuitem><link linkend="clone_pipeline">Clone pipeline</link></guimenuitem></menuchoice> function to duplicate
the currently selected pipeline, or
simply invoke the <menuchoice><guimenu>File</guimenu><guimenuitem>Load File</guimenuitem></menuchoice> function
repeatedly to load multiple datasets into the same scene.
When OVITO asks you whether to replace the already loaded dataset, pick the "<emphasis>Add to scene</emphasis>" option.
All data pipelines in the current scene are listed in the drop-down box at the top
of the command panel (see screenshot). This box selects the pipeline that is currently displayed in the
<link linkend="usage.modification_pipeline.pipeline_listbox">pipeline editor</link> beneath it.
</para>
<para>
Tip: You can reposition each object in the three-dimensional scene as desired using the
Tip: You can reposition objects in the three-dimensional scene using the
<emphasis>Move</emphasis> and <emphasis>Rotate</emphasis> tools found in the upper toolbar.
In this way you can visualize multiple datasets in juxtaposition.
This is useful if you want to visualize multiple datasets in juxtaposition.
</para>
</simplesect>
......
......@@ -137,6 +137,7 @@ namespace Ovito {
OVITO_BEGIN_INLINE_NAMESPACE(Rendering)
class SceneRenderer;
class ObjectPickInfo;
class ViewportPickResult;
class RenderSettings;
class FrameBuffer;
OVITO_BEGIN_INLINE_NAMESPACE(Internal)
......
///////////////////////////////////////////////////////////////////////////////
//
// Copyright (2013) Alexander Stukowski
// Copyright (2018) Alexander Stukowski
//
// This file is part of OVITO (Open Visualization Tool).
//
......@@ -51,13 +51,13 @@ class OVITO_CORE_EXPORT ObjectPickInfo : public OvitoObject
protected:
/// Constructor
ObjectPickInfo() {}
/// Constructor of abstract class.
ObjectPickInfo() = default;
public:
/// Returns a human-readable string describing the picked object, which will be displayed in the status bar by OVITO.
virtual QString infoString(PipelineSceneNode* objectNode, quint32 subobjectId) { return QString(); }
virtual QString infoString(PipelineSceneNode* objectNode, quint32 subobjectId) { return {}; }
};
/**
......@@ -305,7 +305,55 @@ private:
QPointer<SceneRenderer> _renderer;
};
OVITO_END_INLINE_NAMESPACE
} // End of namespace
/*
* Data structure returned by the ViewportWindow::pick() method,
* holding information about the object that was picked in a viewport at the current cursor location.
*/
class OVITO_CORE_EXPORT ViewportPickResult
{
public:
/// Indicates whether an object was picked or not.
bool isValid() const { return _pipelineNode != nullptr; }
/// Returns the scene node that has been picked.
PipelineSceneNode* pipelineNode() const { return _pipelineNode; }
/// Sets the scene node that has been picked.
void setPipelineNode(PipelineSceneNode* node) { _pipelineNode = node; }
/// Returns the object-specific data at the pick location.
ObjectPickInfo* pickInfo() const { return _pickInfo; }
/// Sets the object-specific data at the pick location.
void setPickInfo(ObjectPickInfo* info) { _pickInfo = info; }
/// Returns the coordinates of the hit point in world space.
const Point3& hitLocation() const { return _hitLocation; }
/// Sets the coordinates of the hit point in world space.
void setHitLocation(const Point3& location) { _hitLocation = location; }
/// Returns the subobject that was picked.
quint32 subobjectId() const { return _subobjectId; }
/// Sets the subobject that was picked.
void setSubobjectId(quint32 id) { _subobjectId = id; }
private:
/// The scene node that was picked.
OORef<PipelineSceneNode> _pipelineNode;
/// The object-specific data at the pick location.
OORef<ObjectPickInfo> _pickInfo;
/// The coordinates of the hit point in world space.
Point3 _hitLocation;
/// The subobject that was picked.
quint32 _subobjectId = 0;
};
OVITO_END_INLINE_NAMESPACE
} // End of namespace
......@@ -267,6 +267,8 @@ void ViewportWindow::renderRenderFrame()
******************************************************************************/
ViewportPickResult ViewportWindow::pick(const QPointF& pos)
{
ViewportPickResult result;
// Cannot perform picking while viewport is not visible or currently rendering or when updates are disabled.
if(isVisible() && !viewport()->isRendering() && !viewport()->dataset()->viewportConfig()->isSuspended()) {
try {
......@@ -281,19 +283,17 @@ ViewportPickResult ViewportWindow::pick(const QPointF& pos)
quint32 subobjectId;
std::tie(objInfo, subobjectId) = _pickingRenderer->objectAtLocation(pixelPos);
if(objInfo) {
ViewportPickResult result;
result.objectNode = objInfo->objectNode;
result.pickInfo = objInfo->pickInfo;
result.worldPosition = _pickingRenderer->worldPositionFromLocation(pixelPos);
result.subobjectId = subobjectId;
return result;
result.setPipelineNode(objInfo->objectNode);
result.setPickInfo(objInfo->pickInfo);
result.setHitLocation(_pickingRenderer->worldPositionFromLocation(pixelPos));
result.setSubobjectId(subobjectId);
}
}
catch(const Exception& ex) {
ex.reportError();
}
}
return {};
return result;
}
/******************************************************************************
......
......@@ -24,33 +24,13 @@
#include <gui/GUI.h>
#include <core/viewport/ViewportWindowInterface.h>
#include <core/rendering/SceneRenderer.h>
#include <core/rendering/TextPrimitive.h>
#include <core/rendering/ImagePrimitive.h>
#include <core/rendering/LinePrimitive.h>
namespace Ovito { OVITO_BEGIN_INLINE_NAMESPACE(Gui) OVITO_BEGIN_INLINE_NAMESPACE(Internal)
/******************************************************************************
* This data structure is returned by the Viewport::pick() method.
*******************************************************************************/
struct OVITO_GUI_EXPORT ViewportPickResult
{
/// Indicates whether an object was picked.
explicit operator bool() const { return objectNode != nullptr; }
/// The object node that was picked.
OORef<PipelineSceneNode> objectNode;
/// The object-specific information attached to the pick record.
OORef<ObjectPickInfo> pickInfo;
/// The coordinates of the hit point in world space.
Point3 worldPosition;
/// The subobject that was picked.
quint32 subobjectId = 0;
};
/**
* \brief The internal render window/widget used by the Viewport class.
*/
......
......@@ -419,8 +419,9 @@ void PickOrbitCenterMode::mouseMoveEvent(ViewportWindow* vpwin, QMouseEvent* eve
******************************************************************************/
bool PickOrbitCenterMode::findIntersection(ViewportWindow* vpwin, const QPointF& mousePos, Point3& intersectionPoint)
{
if(ViewportPickResult pickResults = vpwin->pick(mousePos)) {
intersectionPoint = pickResults.worldPosition;
ViewportPickResult pickResult = vpwin->pick(mousePos);
if(pickResult.isValid()) {
intersectionPoint = pickResult.hitLocation();
return true;
}
return false;
......
......@@ -62,9 +62,9 @@ void SelectionMode::mouseReleaseEvent(ViewportWindow* vpwin, QMouseEvent* event)
if(_viewport != nullptr) {
// Select object under mouse cursor.
ViewportPickResult pickResult = vpwin->pick(_clickPoint);
if(pickResult) {
if(pickResult.isValid()) {
_viewport->dataset()->undoStack().beginCompoundOperation(tr("Select"));
_viewport->dataset()->selection()->setNode(pickResult.objectNode);
_viewport->dataset()->selection()->setNode(pickResult.pipelineNode());
_viewport->dataset()->undoStack().endCompoundOperation();
}
_viewport = nullptr;
......@@ -90,11 +90,11 @@ void SelectionMode::mouseMoveEvent(ViewportWindow* vpwin, QMouseEvent* event)
{
// Change mouse cursor while hovering over an object.
ViewportPickResult pickResult = vpwin->pick(event->localPos());
setCursor(pickResult ? selectionCursor() : QCursor());
setCursor(pickResult.isValid() ? selectionCursor() : QCursor());
// Display a description of the object under the mouse cursor in the status bar.
if(pickResult && pickResult.pickInfo)
inputManager()->mainWindow()->statusBar()->showMessage(pickResult.pickInfo->infoString(pickResult.objectNode, pickResult.subobjectId));
if(pickResult.isValid() && pickResult.pickInfo())
inputManager()->mainWindow()->statusBar()->showMessage(pickResult.pickInfo()->infoString(pickResult.pipelineNode(), pickResult.subobjectId()));
else
inputManager()->mainWindow()->statusBar()->clearMessage();
......@@ -185,11 +185,11 @@ void XFormMode::mousePressEvent(ViewportWindow* vpwin, QMouseEvent* event)
// Select object under mouse cursor.
ViewportPickResult pickResult = vpwin->pick(event->localPos());
if(pickResult) {
if(pickResult.isValid()) {
_viewport = vpwin->viewport();
_startPoint = event->localPos();
_viewport->dataset()->undoStack().beginCompoundOperation(undoDisplayName());
_viewport->dataset()->selection()->setNode(pickResult.objectNode);
_viewport->dataset()->selection()->setNode(pickResult.pipelineNode());
_viewport->dataset()->undoStack().beginCompoundOperation(undoDisplayName());
startXForm();
}
......@@ -242,7 +242,7 @@ void XFormMode::mouseMoveEvent(ViewportWindow* vpwin, QMouseEvent* event)
else {
// Change mouse cursor while hovering over an object.
setCursor(vpwin->pick(event->localPos()) ? _xformCursor : QCursor());
setCursor(vpwin->pick(event->localPos()).isValid() ? _xformCursor : QCursor());
}
ViewportInputMode::mouseMoveEvent(vpwin, event);
}
......
......@@ -137,7 +137,7 @@ void CorrelationFunctionModifier::initializeModifier(ModifierApplication* modApp
// Use the first available particle property from the input state as data source when the modifier is newly created.
if(sourceProperty1().isNull() || sourceProperty2().isNull()) {
PipelineFlowState input = modApp->evaluateInputPreliminary();
const PipelineFlowState& input = modApp->evaluateInputPreliminary();
ParticlePropertyReference bestProperty;
for(DataObject* o : input.objects()) {
ParticleProperty* property = dynamic_object_cast<ParticleProperty>(o);
......
......@@ -146,11 +146,11 @@ int DislocationInspectionApplet::PickingMode::pickDislocationSegment(ViewportWin
ViewportPickResult vpPickResult = vpwin->pick(pos);
// Check if user has clicked on something.
if(vpPickResult) {
if(vpPickResult.isValid()) {
// Check if that was a dislocation.
DislocationPickInfo* pickInfo = dynamic_object_cast<DislocationPickInfo>(vpPickResult.pickInfo);
if(pickInfo && vpPickResult.objectNode == _applet->_sceneNode) {
int segmentIndex = pickInfo->segmentIndexFromSubObjectID(vpPickResult.subobjectId);
DislocationPickInfo* pickInfo = dynamic_object_cast<DislocationPickInfo>(vpPickResult.pickInfo());
if(pickInfo && vpPickResult.pipelineNode() == _applet->_sceneNode) {
int segmentIndex = pickInfo->segmentIndexFromSubObjectID(vpPickResult.subobjectId());
if(segmentIndex >= 0 && segmentIndex < pickInfo->dislocationObj()->segments().size()) {
return segmentIndex;
}
......
......@@ -79,7 +79,7 @@ void CreateIsosurfaceModifier::initializeModifier(ModifierApplication* modApp)
// Use the first available property from the input state as data source when the modifier is newly created.
if(sourceProperty().isNull()) {
PipelineFlowState input = modApp->evaluateInputPreliminary();
const PipelineFlowState& input = modApp->evaluateInputPreliminary();
for(DataObject* o : input.objects()) {
VoxelProperty* property = dynamic_object_cast<VoxelProperty>(o);
if(property && property->componentCount() <= 1) {
......
......@@ -54,7 +54,7 @@ public:
virtual void mouseMoveEvent(ViewportWindow* vpwin, QMouseEvent* event) override {
// Change mouse cursor while hovering over an object.
setCursor(vpwin->pick(event->localPos()) ? SelectionMode::selectionCursor() : QCursor());
setCursor(vpwin->pick(event->localPos()).isValid() ? SelectionMode::selectionCursor() : QCursor());
ViewportInputMode::mouseMoveEvent(vpwin, event);
}
......@@ -63,8 +63,8 @@ public:
virtual void mouseReleaseEvent(ViewportWindow* vpwin, QMouseEvent* event) override {
if(event->button() == Qt::LeftButton) {
ViewportPickResult pickResult = vpwin->pick(event->localPos());
if(pickResult && vpwin->viewport()->isPerspectiveProjection()) {
FloatType distance = (pickResult.worldPosition - vpwin->viewport()->cameraPosition()).length();
if(pickResult.isValid() && vpwin->viewport()->isPerspectiveProjection()) {
FloatType distance = (pickResult.hitLocation() - vpwin->viewport()->cameraPosition()).length();
if(OSPRayRenderer* renderer = static_object_cast<OSPRayRenderer>(_editor->editObject())) {
_editor->undoableTransaction(tr("Set focal length"), [renderer, distance]() {
......
......@@ -55,7 +55,6 @@ SET(SourceFiles
modifier/analysis/ptm/PolyhedralTemplateMatchingModifier.cpp
modifier/selection/ExpandSelectionModifier.cpp
modifier/selection/ParticlesExpressionSelectionModifierDelegate.cpp
modifier/selection/ManualSelectionModifier.cpp
modifier/properties/ComputePropertyModifier.cpp
modifier/properties/ComputeBondLengthsModifier.cpp
modifier/properties/InterpolateTrajectoryModifier.cpp
......@@ -92,7 +91,6 @@ SET(SourceFiles
util/NearestNeighborFinder.cpp
util/CutoffNeighborFinder.cpp
util/ParticleExpressionEvaluator.cpp
util/ParticleSelectionSet.cpp
)
IF(OVITO_BUILD_PLUGIN_STDMOD)
......
......@@ -46,7 +46,6 @@ SET(SourceFiles
modifier/analysis/wignerseitz/WignerSeitzAnalysisModifierEditor.cpp
modifier/analysis/ptm/PolyhedralTemplateMatchingModifierEditor.cpp
modifier/selection/ExpandSelectionModifierEditor.cpp
modifier/selection/ManualSelectionModifierEditor.cpp
modifier/properties/ComputePropertyModifierEditor.cpp
modifier/properties/InterpolateTrajectoryModifierEditor.cpp
modifier/properties/GenerateTrajectoryLinesModifierEditor.cpp
......@@ -55,6 +54,7 @@ SET(SourceFiles
util/ParticleSettingsPage.cpp
util/ParticleInspectionApplet.cpp
util/BondInspectionApplet.cpp
util/BondPickingHelper.cpp
import/InputColumnMappingDialog.cpp
import/lammps/LAMMPSTextDumpImporterEditor.cpp
import/lammps/LAMMPSBinaryDumpImporterEditor.cpp
......
......@@ -21,6 +21,8 @@
#include <plugins/particles/gui/ParticlesGui.h>
#include <plugins/particles/objects/BondProperty.h>
#include <gui/actions/ViewportModeAction.h>
#include <gui/mainwin/MainWindow.h>
#include <gui/widgets/general/AutocompleteLineEdit.h>
#include "BondInspectionApplet.h"
......@@ -41,11 +43,16 @@ QWidget* BondInspectionApplet::createWidget(MainWindow* mainWindow)
layout->setContentsMargins(0,0,0,0);
layout->setSpacing(0);
_pickingMode = new PickingMode(this);
ViewportModeAction* pickModeAction = new ViewportModeAction(mainWindow, tr("Select in viewports"), this, _pickingMode);
pickModeAction->setIcon(QIcon(":/particles/icons/select_mode.svg"));
QToolBar* toolbar = new QToolBar();
toolbar->setOrientation(Qt::Horizontal);
toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
toolbar->setIconSize(QSize(18,18));
toolbar->setStyleSheet("QToolBar { padding: 0px; margin: 0px; border: 0px none black; spacing: 0px; }");
toolbar->addAction(pickModeAction);
toolbar->addAction(resetFilterAction());
layout->addWidget(toolbar, 0, 0);
......@@ -53,9 +60,95 @@ QWidget* BondInspectionApplet::createWidget(MainWindow* mainWindow)
layout->addWidget(tableView(), 1, 0, 1, 2);
layout->setRowStretch(1, 1);
QWidget* pickModeButton = toolbar->widgetForAction(pickModeAction);
connect(_pickingMode, &ViewportInputMode::statusChanged, pickModeButton, [pickModeButton,this](bool active) {
if(active) {
QToolTip::showText(pickModeButton->mapToGlobal(pickModeButton->rect().bottomRight()),
#ifndef Q_OS_MACX
tr("Pick a bond in the viewports. Hold down the CONTROL key to select multiple bonds."),
#else
tr("Pick a bond in the viewports. Hold down the COMMAND key to select multiple bonds."),
#endif
pickModeButton, QRect(), 2000);
}
});
connect(filterExpressionEdit(), &AutocompleteLineEdit::editingFinished, this, [this]() {
_pickingMode->resetSelection();
});
return panel;
}
/******************************************************************************
* Updates the contents displayed in the inspector.
******************************************************************************/
void BondInspectionApplet::updateDisplay(const PipelineFlowState& state, PipelineSceneNode* sceneNode)
{
// Clear selection when a different scene node has been selected.
if(sceneNode != currentSceneNode())
_pickingMode->resetSelection();
PropertyInspectionApplet::updateDisplay(state, sceneNode);
}
/******************************************************************************
* This is called when the applet is no longer visible.
******************************************************************************/
void BondInspectionApplet::deactivate(MainWindow* mainWindow)
{
mainWindow->viewportInputManager()->removeInputMode(_pickingMode);
}
/******************************************************************************
* Handles the mouse up events for a Viewport.
******************************************************************************/
void BondInspectionApplet::PickingMode::mouseReleaseEvent(ViewportWindow* vpwin, QMouseEvent* event)
{
if(event->button() == Qt::LeftButton) {
PickResult pickResult;
pickBond(vpwin, event->pos(), pickResult);
if(!event->modifiers().testFlag(Qt::ControlModifier))
_pickedElements.clear();
if(pickResult.sceneNode == _applet->currentSceneNode()) {
// Don't select the same bond twice. Instead, toggle selection.
bool alreadySelected = false;
for(auto p = _pickedElements.begin(); p != _pickedElements.end(); ++p) {
if(p->sceneNode == pickResult.sceneNode && p->bondIndex == pickResult.bondIndex) {
alreadySelected = true;
_pickedElements.erase(p);
break;
}
}
if(!alreadySelected)
_pickedElements.push_back(pickResult);
}
QString filterExpression;
for(const auto& element : _pickedElements) {
if(!filterExpression.isEmpty()) filterExpression += QStringLiteral(" ||\n");
filterExpression += QStringLiteral("BondIndex==%1").arg(element.bondIndex);
}
_applet->setFilterExpression(filterExpression);
requestViewportUpdate();
}
ViewportInputMode::mouseReleaseEvent(vpwin, event);
}
/******************************************************************************
* Handles the mouse move event for the given viewport.
******************************************************************************/
void BondInspectionApplet::PickingMode::mouseMoveEvent(ViewportWindow* vpwin, QMouseEvent* event)
{
// Change mouse cursor while hovering over a bond.
PickResult pickResult;
if(pickBond(vpwin, event->pos(), pickResult) && pickResult.sceneNode == _applet->currentSceneNode())
setCursor(SelectionMode::selectionCursor());
else
setCursor(QCursor());
ViewportInputMode::mouseMoveEvent(vpwin, event);
}
OVITO_END_INLINE_NAMESPACE
OVITO_END_INLINE_NAMESPACE
} // End of namespace
......
......@@ -23,8 +23,12 @@
#include <plugins/particles/gui/ParticlesGui.h>
#include <plugins/particles/gui/util/BondPickingHelper.h>
#include <plugins/particles/util/ParticleExpressionEvaluator.h>
#include <plugins/stdobj/gui/properties/PropertyInspectionApplet.h>
#include <gui/viewport/input/ViewportInputMode.h>
#include <gui/viewport/input/ViewportInputManager.h>
#include <gui/viewport/input/ViewportGizmo.h>
namespace Ovito { namespace Particles { OVITO_BEGIN_INLINE_NAMESPACE(Util) OVITO_BEGIN_INLINE_NAMESPACE(Internal)
......@@ -49,6 +53,12 @@ public:
/// Lets the applet create the UI widget that is to be placed into the data inspector panel.
virtual QWidget* createWidget(MainWindow* mainWindow) override;
/// Lets the applet update the contents displayed in the inspector.
virtual void updateDisplay(const PipelineFlowState& state, PipelineSceneNode* sceneNode) override;
/// This is called when the applet is no longer visible.
virtual void deactivate(MainWindow* mainWindow) override;
protected:
/// Creates the evaluator object for filter expressions.
......@@ -60,6 +70,44 @@ protected:
virtual bool isColorProperty(PropertyObject* property) const override {
return property->type() == BondProperty::ColorProperty;
}
private:
/// Viewport input mode that lets the user pick bonds.
class PickingMode : public ViewportInputMode, BondPickingHelper
{
public:
/// Constructor.
PickingMode(BondInspectionApplet* applet) : ViewportInputMode(applet), _applet(applet) {}
/// Handles the mouse up events for a viewport.
virtual void mouseReleaseEvent(ViewportWindow* vpwin, QMouseEvent* event) override;
/// Handles the mouse move event for the given viewport.