ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PythonPlugin.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "PythonPlugin.h"
9 #include "AboutDialog.h"
11 #include "FileRunner.h"
12 #include "PackageManager.h"
13 #include "PythonActionLauncher.h"
14 #include "PythonRepl.h"
15 #include "PythonRuntimeSettings.h"
16 #include "Resources.h"
17 #include "Runtime/Runtime.h"
18 #include "Utilities.h"
19 
20 #include <QDesktopServices>
21 #include <QMessageBox>
22 #include <QUrl>
23 #include <pybind11/pytypes.h>
24 
25 #define slots Q_SLOTS
26 #define signals Q_SIGNALS
27 #include <QDialog>
28 #include <QFile>
29 #include <QFileDialog>
30 #include <QSettings>
31 #include <algorithm>
33 
34 // Useful link:
35 // https://docs.python.org/3/c-api/init.html#initialization-finalization-and-threads
37  : QObject(parent),
39  m_settings(new PythonRuntimeSettings),
40  m_interp(nullptr),
41  m_editor(new PythonEditor(&m_interp)),
42  m_fileRunner(new FileRunner(&m_interp)),
43  m_actionLauncher(new PythonActionLauncher(&m_pluginManager, &m_interp))
44 {
45  PythonConfig config;
46  bool isDefaultPythonEnv;
47 
49  {
51  isDefaultPythonEnv = false;
52  plgPrint() << "ACloudViewer was loaded from within a " << config.type() << "env. "
53  << "Will try to use it";
54  }
55  else
56  {
57  config = m_settings->pythonEnvConfig();
58  isDefaultPythonEnv = m_settings->isDefaultPythonEnv();
59  if (isDefaultPythonEnv)
60  {
61  plgPrint() << "ACloudViewer was loaded from Bundled env. "
62  << "Will try to use it";
63  }
64  else
65  {
66  plgPrint() << "ACloudViewer was loaded from " << config.type() << "env. "
67  << "Will try to use it";
68  }
69  }
70 
71  if (!isDefaultPythonEnv)
72  {
73  bool seemsValid = config.validateAndDisplayErrors();
74  if (!seemsValid)
75  {
76  plgWarning()
77  << "Falling back to default bundled Python configuration due to previous errors";
78  config.initBundled();
79  }
80  }
81 
82  try
83  {
84  m_interp.initialize(config);
85  }
86  catch (const std::exception &e)
87  {
88  plgPrint() << "Current Python config: " << config;
89  plgError() << "Failed to initialize Python: " << e.what();
90  return;
91  }
92 
93  m_config = config;
94  m_pluginsMenu = new QMenu("Plugins");
95  m_pluginsMenu->setToolTip("Python Plugins");
96  m_pluginsMenu->setEnabled(false);
97  m_pluginsMenu->setIcon(QIcon(PYPLUGIN_ICON_PATH));
98 
99  connect(&m_interp,
101  this,
102  &PythonPlugin::handlePythonExecutionStarted);
103 
104  connect(&m_interp,
106  this,
107  &PythonPlugin::handlePythonExecutionFinished);
108 
109  // Note: We do NOT call py::finalize_interpreter() on quit anymore.
110  // With embedded Python modules (PYBIND11_EMBEDDED_MODULE), calling finalize
111  // can cause crashes due to static destruction order issues. It's safer to let
112  // the OS clean up the Python interpreter when the process exits.
113  // See: https://github.com/pybind/pybind11/issues/1598
114  //
115  // connect(QCoreApplication::instance(),
116  // &QCoreApplication::aboutToQuit,
117  // this,
118  // &PythonPlugin::finalizeInterpreter);
119 }
120 
121 static std::unique_ptr<QSettings> LoadSettings()
122 {
123  return std::make_unique<QSettings>(
124  QCoreApplication::organizationName(),
125  QCoreApplication::applicationName().append(":PythonRuntime.Settings"));
126 }
127 
129 {
130 
131  // On software exit, the script list needs to be saved in a txt file
132  std::unique_ptr<QSettings> settings = LoadSettings();
133  settings->setValue(QStringLiteral("RegisterListPath"), m_savedPath);
134 }
135 
136 QList<QAction *> PythonPlugin::getActions()
137 {
138  const bool isPythonProperlyInitialized = PythonInterpreter::IsInitialized();
139 
140  if (!m_showEditor)
141  {
142  m_showEditor = new QAction("Show Editor", this);
143  m_showEditor->setToolTip("Show the code editor window");
144  m_showEditor->setIcon(QIcon(EDITOR_ICON_PATH));
145  connect(m_showEditor, &QAction::triggered, this, &PythonPlugin::showEditor);
146  m_showEditor->setEnabled(isPythonProperlyInitialized);
147  }
148 
149  if (!m_showRepl)
150  {
151  m_showRepl = new QAction("Show REPL", this);
152  m_showRepl->setToolTip("Show the Python REPL");
153  m_showRepl->setIcon(QIcon(REPL_ICON_PATH));
154  connect(m_showRepl, &QAction::triggered, this, &PythonPlugin::showRepl);
155  m_showRepl->setEnabled(isPythonProperlyInitialized);
156  }
157 
158  if (!m_showDoc)
159  {
160  m_showDoc = new QAction("Show Documentation", this);
161  m_showDoc->setToolTip("Open online documentation in your web browser");
162  m_showDoc->setIcon(QIcon(DOCUMENTATION_ICON_PATH));
163  connect(m_showDoc, &QAction::triggered, &PythonPlugin::showDocumentation);
164  m_showDoc->setEnabled(isPythonProperlyInitialized);
165  }
166 
167  if (!m_showAboutDialog)
168  {
169  m_showAboutDialog = new QAction("About", this);
170  m_showAboutDialog->setToolTip("About this plugin");
171  m_showAboutDialog->setIcon(QIcon(ABOUT_ICON_PATH));
172  connect(m_showAboutDialog, &QAction::triggered, this, &PythonPlugin::showAboutDialog);
173  m_showAboutDialog->setEnabled(isPythonProperlyInitialized);
174  }
175 
176  if (!m_showFileRunner)
177  {
178  m_showFileRunner = new QAction("File Runner", this);
179  m_showFileRunner->setToolTip("Small widget to select and run a script");
180  m_showFileRunner->setIcon(QIcon(RUNNER_ICON_PATH));
181  connect(m_showFileRunner, &QAction::triggered, this, &PythonPlugin::showFileRunner);
182  m_showFileRunner->setEnabled(isPythonProperlyInitialized);
183  }
184 
185  if (!m_showPackageManager)
186  {
187  m_showPackageManager = new QAction("Package Manager", this);
188  m_showPackageManager->setToolTip("Manage packages with pip");
189  m_showPackageManager->setIcon(QIcon(PACKAGE_MANAGER_ICON_PATH));
190  connect(m_showPackageManager, &QAction::triggered, this, &PythonPlugin::showPackageManager);
191  m_showPackageManager->setEnabled(isPythonProperlyInitialized);
192  }
193 
194  if (!m_showActionLauncher)
195  {
196  m_showActionLauncher = new QAction("Show Action Launcher", this);
197  m_showActionLauncher->setIcon(QIcon(ACTION_LAUNCHER_ICON_PATH));
198  m_showActionLauncher->setToolTip("Launch actions of custom Python plugins");
199  connect(m_showActionLauncher,
200  &QAction::triggered,
201  this,
202  &PythonPlugin::showPythonActionLauncher);
203  m_showActionLauncher->setEnabled(isPythonProperlyInitialized);
204  }
205 
206  if (!m_showSettings)
207  {
208  m_showSettings = new QAction("Show Settings", this);
209  m_showSettings->setIcon(QIcon(SETTINGS_ICON_PATH));
210  m_showSettings->setToolTip("Show some settings");
211  connect(m_showSettings, &QAction::triggered, this, &PythonPlugin::showSettings);
212  // Settings do not need Python to be initialized in a valid state
213  m_showSettings->setEnabled(true);
214  }
215  if (!m_drawScriptRegister)
216  {
217  m_drawScriptRegister = new QMenu("Script Register");
218  m_drawScriptRegister->setToolTip("Show all registered script");
219  m_drawScriptRegister->setEnabled(true);
220  m_drawScriptRegister->setIcon(QIcon(PYSCRIPTS_REGISTER_ICON_PATH));
221 
222  m_addScript = new QAction("Add Script");
223  m_addScript->setToolTip("Add Script");
224  m_addScript->setIcon(QIcon(ADD_PYSCRIPT_ICON_PATH));
225  connect(m_addScript, &QAction::triggered, this, &PythonPlugin::addScriptAction);
226  m_addScript->setEnabled(true);
227 
228  m_removeScript = new QMenu("Remove Script");
229  m_removeScript->setIcon(QIcon(REMOVE_PYSCRIPT_ICON_PATH));
230  m_removeScript->setToolTip("Remove Script");
231  m_removeScript->setEnabled(false);
232 
233  m_drawScriptRegister->addAction(m_addScript);
234  m_drawScriptRegister->addMenu(m_removeScript);
235  m_drawScriptRegister->addSeparator();
236 
237  std::unique_ptr<QSettings> settings = LoadSettings();
238  QStringList loaded_paths =
239  settings->value(QStringLiteral("RegisterListPath")).value<QStringList>();
240 
241  for (QString path : loaded_paths)
242  {
243  QFileInfo fi(path);
244  if (!fi.exists())
245  plgPrint() << "Script registered \"" << path << "\" doesn't exist.";
246  else
247  addScript(path);
248  }
249  }
250 
251  return {
252  m_showEditor,
253  m_showFileRunner,
254  m_showAboutDialog,
255  m_showDoc,
256  m_showRepl,
257  m_showPackageManager,
258  m_showActionLauncher,
259  m_showSettings,
260  m_drawScriptRegister->menuAction(),
261  m_pluginsMenu->menuAction(),
262  };
263 }
264 
265 void PythonPlugin::showRepl()
266 {
267  if (!m_interp.IsInitialized())
268  {
269  QMessageBox::critical(
270  nullptr,
271  "Python Interpreter Not Initialized",
272  "The Python interpreter failed to initialize. Cannot open the interactive REPL "
273  "window.\nPlease check your Python environment configuration or logs.");
274  return;
275  }
276 
277  if (m_repl)
278  {
279  m_repl->show();
280  m_repl->raise();
281  m_repl->activateWindow();
282  }
283  else
284  {
285  m_repl = new PythonRepl(&m_interp);
286  m_repl->show();
287  }
288 }
289 
290 void PythonPlugin::showEditor() const
291 {
292  if (m_editor)
293  {
294  m_editor->show();
295  m_editor->raise();
296  m_editor->activateWindow();
297  }
298 }
299 
300 void PythonPlugin::addScriptAction()
301 {
302  if (m_scriptList.empty())
303  m_removeScript->setEnabled(true);
304 
305  QString filePath = QFileDialog::getOpenFileName(m_drawScriptRegister,
306  QStringLiteral("Select Python Script"),
307  QString(),
308  QStringLiteral("Python Script (*.py)"));
309  addScript(filePath);
310 }
311 
312 void PythonPlugin::addScript(QString path)
313 {
314  if (m_scriptList.empty())
315  m_removeScript->setEnabled(true);
316 
317  QFileInfo fi(path);
318 
319  // Doesn't add if file doesn't exist or if it's already present.
320  if (!fi.exists() || m_savedPath.contains(path))
321  return;
322 
323  QString fileName = fi.baseName();
324 
325  auto *newScript = new QAction(fileName);
326  newScript->setToolTip(fileName);
327  newScript->setIcon(QIcon(PYSCRIPT_ICON_PATH));
328  auto *removeNewScript = new QAction(fileName);
329  removeNewScript->setToolTip(fileName);
330  removeNewScript->setIcon(QIcon(PYSCRIPT_ICON_PATH));
331 
332  connect(newScript, &QAction::triggered, [this, path]() { executeScript(path); });
333  newScript->setEnabled(true);
334  connect(removeNewScript,
335  &QAction::triggered,
336  [this, fileName, removeNewScript, path]()
337  {
338  removeScript(fileName, removeNewScript);
339 
340  // prepare to save script list
341  auto pos = std::find(m_savedPath.begin(), m_savedPath.end(), path);
342  if (pos != m_savedPath.end())
343  {
344  m_savedPath.erase(pos);
345  }
346  });
347  removeNewScript->setEnabled(true);
348 
349  m_scriptList.insert({fileName, newScript});
350  m_drawScriptRegister->addAction(newScript);
351  m_removeScript->addAction(removeNewScript);
352 
353  // prepare to save script list
354  m_savedPath.push_back(path);
355 }
356 
357 void PythonPlugin::executeScript(QString path)
358 {
359  const std::string path_str = path.toStdString();
360  m_interp.executeFile(path_str);
361 }
362 
363 void PythonPlugin::removeScript(QString name, QAction *self)
364 {
365  QAction *script = m_scriptList[name];
366  m_drawScriptRegister->removeAction(script);
367  m_removeScript->removeAction(self);
368  m_scriptList.erase(name);
369  delete script;
370  delete self;
371  if (m_scriptList.empty())
372  m_removeScript->setEnabled(false);
373 }
374 
375 void PythonPlugin::handlePluginActionClicked(bool)
376 {
377  const auto *qAction = static_cast<QAction *>(sender());
378  try
379  {
380  auto action = m_pluginActions.at(qAction);
381  m_interp.executeFunction(action->target);
382  }
383  catch (const std::exception &e)
384  {
385  plgError() << "Failed to launch plugin action: '" << e.what() << "'";
386  }
387 }
388 
389 void PythonPlugin::showFileRunner() const
390 {
391  m_fileRunner->show();
392 }
393 
394 void PythonPlugin::showDocumentation()
395 {
396  const QUrl url(QString("https://tmontaigu.github.io/CloudCompare-PythonRuntime/index.html"));
397  QDesktopServices::openUrl(url);
398 }
399 
400 void PythonPlugin::showAboutDialog() const
401 {
403  dlg.exec();
404 }
405 
406 void PythonPlugin::showPackageManager()
407 {
408  if (m_packageManager == nullptr)
409  {
410  m_packageManager = new PackageManager(m_config);
411  }
412  m_packageManager->show();
413  m_editor->raise();
414  m_editor->activateWindow();
415 }
416 
417 void PythonPlugin::showPythonActionLauncher() const
418 {
419  m_actionLauncher->show();
420 }
421 
422 void PythonPlugin::showSettings() const
423 {
424  m_settings->show();
425 }
426 
428 {
431 }
432 
434 {
435  explicit PythonPluginCommand(PythonInterpreter *interpreter_)
436  : Command("PYTHON", "PYTHON_SCRIPT"), interpreter(interpreter_)
437  {
438  }
439 
440  bool process(ccCommandLineInterface &cmd) override
441  {
442  Q_ASSERT(interpreter);
443  cmd.print("[PythonRuntime] Starting");
444  Args args;
445  if (!args.parseFrom(cmd))
446  {
447  return false;
448  }
449 
450  if (!Py_IsInitialized())
451  {
452  return cmd.error("[PythonRuntime] Python is not properly initialized");
453  }
454 
455  PySys_SetArgvEx(static_cast<int>(args.pythonArgv.size()), args.pythonArgv.data(), 1);
456  const bool success = interpreter->executeFile(qPrintable(args.filepath));
457 
458  cmd.print(QString("[PythonRuntime] Script %1 executed")
459  .arg(success ? "successfully" : "unsuccessfully"));
460  return success;
461  }
462 
463  struct Args
464  {
465  QString filepath;
466  std::vector<wchar_t *> pythonArgv{};
467 
468  Args() = default;
469 
471  {
472  if (cmd.arguments().empty())
473  {
474  return cmd.error(QString("Missing parameter: parameters filename after \"-%1\"")
475  .arg("PYTHON_SCRIPT"));
476  }
477  filepath = cmd.arguments().takeFirst();
478 
479  pythonArgv.reserve(cmd.arguments().size() + 1);
481  while (!cmd.arguments().isEmpty())
482  {
483  pythonArgv.push_back(QStringToWcharArray(cmd.arguments().takeFirst()));
484  }
485  return true;
486  }
487 
488  virtual ~Args()
489  {
490  for (wchar_t *arg : pythonArgv)
491  {
492  delete[] arg;
493  }
494  }
495  };
496 
498 };
499 
501 {
502  cmd->registerCommand(
505 }
506 
507 // We use this as our last step in the initialization process
508 // since many of our stuff relies on the app interface to be valid
510 {
511  LogPythonHome();
512  LogPythonPath();
513 
516 
517  // Now that the mainAppInterface is set, we can load custom
518  // python plugins, if we did this earlier, `pycc.GetInstance()`
519  // in python would return `None` and that's bad.
520 
521  // Start by autodiscovering plugins from metadata
522  try
523  {
524  m_pluginManager.loadPluginsFromEntryPoints();
525  }
526  catch (const std::exception &e)
527  {
528  CVLog::Warning("[PythonRuntime] Failed to load autodiscovered custom python plugins: %s",
529  e.what());
530  }
531 
532  // In the end, we add plugins from custom paths
533  try
534  {
535  m_pluginManager.loadPluginsFrom(m_settings->pluginsPaths());
536  }
537  catch (const std::exception &e)
538  {
539  CVLog::Warning("[PythonRuntime] Failed to load custom python plugins : %s", e.what());
540  }
541 
542  populatePluginSubMenu();
543 
544  m_fileRunner->setParent(m_app->getMainWindow(), Qt::Window);
545  m_actionLauncher->setParent(m_app->getMainWindow(), Qt::Window);
546  m_settings->setParent(m_app->getMainWindow(), Qt::Window);
547 }
548 
554 static QIcon CreateQIconFromPyObject(const py::object &pyIcon)
555 {
556  QIcon icon{};
557  if (py::isinstance<py::str>(pyIcon))
558  {
559  const auto filePath = pyIcon.cast<std::string>();
560  const auto qFilePath = QString::fromStdString(filePath);
561  icon = QIcon(qFilePath);
562  }
563  else if (py::isinstance<py::bytes>(pyIcon))
564  {
565  auto bytes = pyIcon.cast<std::string>();
566  QPixmap pixmap;
567  auto size = static_cast<uint>(bytes.size());
568  bool ok = pixmap.loadFromData(
569  reinterpret_cast<const uchar *>(bytes.c_str()), size, nullptr /*format=*/);
570  if (!ok)
571  {
572  plgError() << "Failed to load icon from bytes";
573  }
574  icon = QIcon(pixmap);
575  }
576  else if (py::isinstance<py::tuple>(pyIcon))
577  {
578  auto icon_tuple = pyIcon.cast<py::tuple>();
579 
580  std::string bytes{};
581  try
582  {
583  bytes = icon_tuple[0].cast<std::string>();
584  }
585  catch (const std::exception &)
586  {
587  plgWarning() << "Invalid tuple member for icon, expected (bytes, str)";
588  }
589 
590  std::string format{};
591  try
592  {
593  format = icon_tuple[1].cast<std::string>();
594  }
595  catch (const std::exception &)
596  {
597  plgWarning() << "Invalid tuple member for icon, expected (bytes, str)";
598  }
599 
600  if (!bytes.empty())
601  {
602  QPixmap pixmap;
603  // Here format may be empty, meaning its c_str is nullptr,
604  // that is okay, loadFromData accepts nullptr for the format
605  auto size = static_cast<uint>(bytes.size());
606  bool ok = pixmap.loadFromData(
607  reinterpret_cast<const uchar *>(bytes.c_str()), size, format.c_str());
608  if (!ok)
609  {
610  plgError() << "Failed to load icon from bytes";
611  }
612  icon = QIcon(pixmap);
613  }
614  }
615  return icon;
616 }
617 
618 template <typename T> static bool SetIconFromPyObject(T *iconReceiver, const py::object &pyIcon)
619 {
620  if (pyIcon.is_none())
621  {
622  return false;
623  }
624 
625  QIcon icon = CreateQIconFromPyObject(pyIcon);
626  if (!icon.isNull())
627  {
628  iconReceiver->setIcon(icon);
629  return true;
630  }
631 
632  return false;
633 }
634 
635 void PythonPlugin::populatePluginSubMenu()
636 {
637  for (const Runtime::RegisteredPlugin &plugin : m_pluginManager.plugins())
638  {
639  if (plugin.actions.size() > 1)
640  {
641  auto *menu = new QMenu(plugin.name);
642  SetIconFromPyObject(menu, plugin.mainIcon);
643 
644  for (const Runtime::RegisteredPlugin::Action &action : plugin.actions)
645  {
646  auto *qAction = new QAction(action.name);
647  qAction->setParent(menu);
648  menu->addAction(qAction);
649 
650  SetIconFromPyObject(qAction, action.icon);
651 
652  connect(
653  qAction, &QAction::triggered, this, &PythonPlugin::handlePluginActionClicked);
654  m_pluginActions[qAction] = &action;
655  }
656  m_pluginsMenu->addMenu(menu);
657  }
658  else
659  {
660  auto *qAction = new QAction(plugin.actions[0].name);
661  if (!plugin.actions[0].icon.is_none())
662  {
663  QIcon icon = CreateQIconFromPyObject(plugin.actions[0].icon);
664  bool iconWasSet = SetIconFromPyObject(qAction, plugin.actions[0].icon);
665  if (!iconWasSet)
666  {
667  SetIconFromPyObject(qAction, plugin.mainIcon);
668  }
669  }
670  connect(qAction, &QAction::triggered, this, &PythonPlugin::handlePluginActionClicked);
671  m_pluginsMenu->addAction(qAction);
672  m_pluginActions[qAction] = &plugin.actions[0];
673  }
674  }
675  m_pluginsMenu->setEnabled(!m_pluginsMenu->isEmpty());
676 }
677 
678 void PythonPlugin::handlePythonExecutionStarted()
679 {
680  m_pluginsMenu->setEnabled(false);
681 }
682 void PythonPlugin::handlePythonExecutionFinished()
683 {
684  m_pluginsMenu->setEnabled(true);
685 }
686 
687 void PythonPlugin::finalizeInterpreter()
688 {
689  // We have to clear registered functions before
690  // we finalize the interpreter otherwise our references to
691  // python object would outlive the interpreter
692  m_pluginManager.unloadPlugins();
693  m_interp.finalize();
694 }
filament::Texture::InternalFormat format
int size
std::string name
static std::unique_ptr< QSettings > LoadSettings()
static QIcon CreateQIconFromPyObject(const py::object &pyIcon)
static bool SetIconFromPyObject(T *iconReceiver, const py::object &pyIcon)
#define PLUGIN_INFO_PATH
Definition: Resources.h:12
#define REPL_ICON_PATH
Definition: Resources.h:15
#define PYPLUGIN_ICON_PATH
Definition: Resources.h:24
#define REMOVE_PYSCRIPT_ICON_PATH
Definition: Resources.h:30
#define ABOUT_ICON_PATH
Definition: Resources.h:13
#define ADD_PYSCRIPT_ICON_PATH
Definition: Resources.h:28
#define EDITOR_ICON_PATH
Definition: Resources.h:14
#define RUNNER_ICON_PATH
Definition: Resources.h:18
#define PACKAGE_MANAGER_ICON_PATH
Definition: Resources.h:19
#define ACTION_LAUNCHER_ICON_PATH
Definition: Resources.h:21
#define PYSCRIPT_ICON_PATH
Definition: Resources.h:32
#define PYSCRIPTS_REGISTER_ICON_PATH
Definition: Resources.h:26
#define DOCUMENTATION_ICON_PATH
Definition: Resources.h:16
#define SETTINGS_ICON_PATH
Definition: Resources.h:23
void * bytes
PluginLogger< CVLog::LOG_WARNING > plgWarning
Definition: Utilities.h:102
void LogPythonPath()
Logs the PYTHON_PATH the log console of ACloudViewer.
Definition: Utilities.h:116
PluginLogger< CVLog::LOG_ERROR > plgError
Definition: Utilities.h:103
wchar_t * QStringToWcharArray(const QString &string)
Returns a newly allocated wchar_t array (null terminated) from a QString.
Definition: Utilities.h:106
PluginLogger< CVLog::LOG_STANDARD > plgPrint
Definition: Utilities.h:100
void LogPythonHome()
Logs the PYTHON_HOME the log console of ACloudViewer.
Definition: Utilities.h:140
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
void initBundled()
static PythonConfig fromContainingEnvironment()
bool validateAndDisplayErrors(QWidget *parent=nullptr) const
Type type() const
Definition: PythonConfig.h:84
static bool IsInsideEnvironment()
void executeFunction(const pybind11::object &function)
void executionFinished()
static bool IsInitialized()
bool executeFile(const std::string &filePath)
Execution functions (and slots)
void initialize(const PythonConfig &config)
void loadPluginsFrom(const QStringList &paths)
const std::vector< Runtime::RegisteredPlugin > & plugins() const
Returns the currently loaded plugins.
void unloadPlugins()
This MUST be called before finalizing the interpreter.
void setMainAppInterface(ecvMainAppInterface *app) override
Sets application entry point.
QList< QAction * > getActions() override
Get a list of actions for this plugin.
void stop() override
Stops the plugin.
PythonPlugin(QObject *parent=nullptr)
void registerCommands(ccCommandLineInterface *cmd) override
~PythonPlugin() noexcept override
Homemade REPL (Read Print Eval Loop)
Definition: PythonRepl.h:59
QStringList pluginsPaths() const
PythonConfig pythonEnvConfig() const
Command line interface.
virtual QStringList & arguments()=0
Returns the list of arguments.
virtual void print(const QString &message) const =0
virtual bool error(const QString &message) const =0
virtual bool registerCommand(Command::Shared command)=0
Registers a new command.
Standard ECV plugin interface.
virtual void setMainAppInterface(ecvMainAppInterface *app)
Sets application entry point.
ecvMainAppInterface * m_app
Main application interface.
Main application interface (for plugins)
virtual QMainWindow * getMainWindow()=0
Returns main window.
unsigned int uint
Definition: cutil_math.h:28
void setMainAppInterfaceInstance(ecvMainAppInterface *appInterface) noexcept(false)
Definition: Runtime.cpp:82
void unsetMainAppInterfaceInstance() noexcept
Unsets the app interface pointer.
Definition: Runtime.cpp:90
void setCmdLineInterfaceInstance(ccCommandLineInterface *cmdLine) noexcept
Definition: Runtime.cpp:99
void unsetCmdLineInterfaceInstance() noexcept
Unsets the pointer to the cmdline app interface.
Definition: Runtime.cpp:107
static const std::string path
Definition: PointCloud.cpp:59
unsigned char uchar
Definition: matrix.h:41
struct Window Window
Definition: sqlite3.c:14678
bool parseFrom(ccCommandLineInterface &cmd)
std::vector< wchar_t * > pythonArgv
PythonInterpreter * interpreter
PythonPluginCommand(PythonInterpreter *interpreter_)
bool process(ccCommandLineInterface &cmd) override
Main process.
QString name
Name to be displayed in the UI.
Definition: Runtime.h:32
pybind11::object icon
Optional path or (bytes, str) where str is the format.
Definition: Runtime.h:36
QSharedPointer< Command > Shared
Shared type.
Command(const QString &name, const QString &keyword)
Default constructor.