BornAgain  1.19.79
Simulate and fit neutron and x-ray scattering at grazing incidence
PyImportAssistant.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/View/Project/PyImportAssistant.cpp
6 //! @brief Implements class PyImportAssistant
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
15 #ifdef BORNAGAIN_PYTHON
16 
18 #include "BABuild.h"
19 #include "Base/Util/Assert.h"
20 #include "Base/Util/SysUtils.h"
25 #include "GUI/Util/Path.h"
26 #include "GUI/Util/String.h"
32 #include "Sample/Multilayer/MultiLayer.h"
33 #include "Sample/Multilayer/PyImport.h"
34 #include <QApplication>
35 #include <QFileDialog>
36 #include <QTextStream>
37 
38 namespace {
39 
40 //! Returns directory with BornAgain library. If PYTHONPATH is not empty,
41 //! Returns an empty string.
42 
43 std::string bornagainDir()
44 {
45  std::string pythonPath = BaseUtils::System::getenv("PYTHONPATH");
46  return pythonPath.empty() ? BABuild::buildLibDir() : "";
47 }
48 
49 //! Returns a name from the list which looks like a function name intended for sample
50 //! creation.
51 
52 QString getCandidate(const QStringList& funcNames)
53 {
54  if (funcNames.isEmpty())
55  return "";
56 
57  for (auto str : funcNames) {
58  QString name = str.toLower();
59  if (name.contains("sample") || name.contains("sample"))
60  return str;
61  }
62 
63  return funcNames.front();
64 }
65 
66 //! Lets user to select Python file on disk.
67 
68 QString fileNameToOpen()
69 {
70  QString dirname = ProjectManager::instance()->userImportDir();
71 
72  QString result = QFileDialog::getOpenFileName(
73  GUI::Global::mainWindow, "Open python script", dirname, "Python scripts (*.py)", nullptr,
74  appSettings->useNativeFileDialog() ? QFileDialog::Options()
75  : QFileDialog::DontUseNativeDialog);
76 
77  if (!result.isEmpty())
79 
80  return result;
81 }
82 
83 //! Read content of text file and returns it as a multi-line string.
84 //! Pop-ups warning dialog in the case of failure.
85 
86 QString readFile(const QString& fileName)
87 {
88  QFile file(fileName);
89  if (file.open(QIODevice::ReadOnly | QIODevice::Text))
90  return QTextStream(&file).readAll();
91 
92  const QString message =
93  "Can't read the file. \n\nPyImportAssistant::readFile -> Error: Can' t open the file '"
94  + fileName + "' for reading.";
95  GUI::View::Helpers::warning(GUI::Global::mainWindow, "File read failure.", message);
96  return {};
97 }
98 
99 //! Creates a multi-layer by executing a funcName in embedded Python.
100 //! Function is supposed to be in code provided by 'snippet'.
101 
102 std::unique_ptr<MultiLayer> createMultiLayer(const QString& snippet, const QString& funcName)
103 {
104  std::unique_ptr<MultiLayer> result;
105 
106  QApplication::setOverrideCursor(Qt::WaitCursor);
107  try {
108  result = Py::Import::createFromPython(snippet.toStdString(), funcName.toStdString(),
109  bornagainDir());
110  } catch (const std::exception& ex) {
111  QApplication::restoreOverrideCursor();
112  QString message("Exception thrown while executing Python code to create sample.\n\n");
113  QString details = QString::fromStdString(std::string(ex.what()));
114  DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec();
115  }
116  QApplication::restoreOverrideCursor();
117 
118  return result;
119 }
120 
121 //! Lets user select a function name which generates a MultiLayer.
122 
123 QString selectPySampleFunction(const QStringList& funcNames)
124 {
125  QString result;
126 
127  if (funcNames.empty()) {
128  QString message("Python code doesn't contain any functions.\n\n");
129  GUI::View::Helpers::warning(GUI::Global::mainWindow, "Python failure", message);
130  } else if (funcNames.size() == 1)
131  return funcNames.front();
132  else {
133  ComboSelectorDialog dialog;
134  dialog.addItems(funcNames, getCandidate(funcNames));
135  dialog.setTextTop("Python code contains a few functions. Do you know by chance, "
136  "which one is intended to produce a valid MultiLayer?");
137  dialog.setTextBottom(
138  "Please select a valid function in combo box and press OK to continue.");
139  if (dialog.exec() == QDialog::Accepted)
140  result = dialog.currentText();
141  }
142 
143  return result;
144 }
145 
146 //! Returns the name of function which might generate a MultiLayer in Python code snippet.
147 //! Pop-ups dialog and asks user for help in the case of doubts.
148 
149 QString getPySampleFunctionName(const QString& snippet)
150 {
151  QStringList funcList;
152 
153  QApplication::setOverrideCursor(Qt::WaitCursor);
154  try {
155  auto funcs = Py::Import::listOfFunctions(snippet.toStdString(), bornagainDir());
156  funcList = GUI::Util::String::fromStdStrings(funcs);
157  } catch (const std::exception& ex) {
158  QApplication::restoreOverrideCursor();
159  QString message("Exception thrown while acquiring functions from Python code.\n\n");
160  QString details = QString::fromStdString(std::string(ex.what()));
161  DetailedMessageBox(GUI::Global::mainWindow, "Python failure", message, details).exec();
162 
163  return "";
164  }
165  QApplication::restoreOverrideCursor();
166 
167  return selectPySampleFunction(funcList);
168 }
169 
170 } // namespace
171 
172 std::unique_ptr<MultiLayer> PyImportAssistant::importMultiLayer()
173 {
174  auto fileName = fileNameToOpen();
175 
176  if (fileName.isEmpty())
177  return {};
178 
179  QString snippet = readFile(fileName);
180  if (snippet.isEmpty())
181  return {};
182 
183  QString funcName = getPySampleFunctionName(snippet);
184  if (funcName.isEmpty())
185  return {};
186 
187  auto sample = createMultiLayer(snippet, funcName);
188  if (!sample)
189  return {};
190 
191  if (sample->sampleName() == "Unnamed")
192  sample->setSampleName(GUI::Util::Path::baseName(fileName).toStdString());
193 
194  return sample;
195 }
196 
198 {
199  try {
200  auto* newItem = GUI::Transform::FromCore::itemizeSample(sample);
201 
202  QString message("Seems that import was successful.\n\n"
203  "Check SampleView for new sample and material editor for new materials.");
204  GUI::View::Helpers::information(GUI::Global::mainWindow, "Python import", message);
205  return newItem;
206  } catch (const std::exception& ex) {
207  QString message("Exception thrown while trying to build GUI models.\n"
208  "GUI models might be in inconsistent state.\n\n");
209  QString details = QString::fromStdString(std::string(ex.what()));
210  DetailedMessageBox(GUI::Global::mainWindow, "GUI::Model::ObjectBuilder failure", message,
211  details)
212  .exec();
213  return nullptr;
214  }
215 }
216 
217 #endif // BORNAGAIN_PYTHON
ApplicationSettings * appSettings
global pointer to the instance
Defines class ApplicationSettings.
Defines class ComboSelectorDialog.
Defines class DetailedMessageBox.
Defines global pointers.
Defines class GUISampleBuilder.
Defines namespace GUI::Model::ObjectBuilder.
Defines class GUIHelpers functions.
Defines class Helpers functions.
Defines class ProjectManager.
Defines namespace GUI::Project::Utils.
Implements class PyImportAssistant.
Defines functions from namespace GUI::Util::String.
bool useNativeFileDialog() const
A dialog similar to standard QMessageBox with combo box selector.
void addItems(const QStringList &selection, const QString &currentItem="")
void setTextTop(const QString &text)
void setTextBottom(const QString &text)
QString currentText() const
A dialog similar to standard QMessageBox intended for detailed warning messages. On the contrary to Q...
static ProjectManager * instance()
void setImportDir(const QString &dirname)
Sets user import directory in system settings.
QString userImportDir() const
Returns directory name which was used by the user to import files.
static QMainWindow * mainWindow
Definition: Globals.h:22
QString const & name(EShape k)
Definition: particles.cpp:20
MultiLayerItem * itemizeSample(const MultiLayer &sample, const QString &nodeName)
Builds GUI sample structure from a domain sample structure.
QString baseName(const QString &fileName)
Returns base name of file.
Definition: Path.cpp:133
QString fileDir(const QString &fileName)
Returns file directory from the full file path.
Definition: Path.cpp:123
QStringList fromStdStrings(const std::vector< std::string > &container)
Definition: String.cpp:18
void information(QWidget *parent, const QString &title, const QString &text, const QString &detailedText)
Definition: MessageBox.cpp:22
void warning(QWidget *parent, const QString &title, const QString &text, const QString &detailedText)
Definition: MessageBox.cpp:37
MultiLayerItem * itemizeSample(const MultiLayer &sample)
Populates GUI sample with domain sample.
std::unique_ptr< MultiLayer > importMultiLayer()
Show select-file dialog, try to import via python, return created sample.