using_notebook.rst
themes.rst
using_catalog_generator.rst
- working_with_python_scripts.rst
+ working_with_python_scripts.rst
+ working_with_python_console.rst
using_registry_tool.rst
viewers_chapter.rst
setting_preferences.rst
--- /dev/null
+.. _python_console_page:
+
+******************
+Python Console
+******************
+
+**Python console** - Window for Python interpreter. This window functions like a standard document:
+the pop-up menu invoked by right-click in this window gives access to **Copy/Paste/SelectAll/ClearAll** options.
+
+You can run a Python script in interactive mode by typing expressions line by line or by loading a Python file
+from the main menu **File -> Load Script**.
+
+==================
+Asynchronous mode
+==================
+
+By default the console is always initialized in synchronous mode. It means that each Python command is executed in the main GUI thread,
+then GUI is blocked until the command is finished. It was done this way to prevent crashes of PARAVIS scripts,
+when VTK does not run in the main thread.
+
+However, it could be an issue if you run a time consuming Python script, because you won't see any output till the end of the execution.
+
+In this case you can switch on the asynchronous mode, when the Python commands are executed in the separated thread,
+that doesn't block the main GUI loop. So, you'll see any intermediate output from the running script even if the script didn't finish yet.
+
+To switch asynchronous mode on, set ``PYTHON_CONSOLE_SYNC`` environment variable to any value less or equal to 0 before Salome start:
+
+.. code-block:: console
+
+ export PYTHON_CONSOLE_SYNC=0
+
+To return back to synchronous mode, set ``PYTHON_CONSOLE_SYNC`` to any value greater than 0. Currently undefined ``PYTHON_CONSOLE_SYNC``
+switches to sync mode as well for compatibility reasons.
+
+==================
+Tracing
+==================
+
+To output in console currently executed functions, we're adding tracing code to the start and at the end
+of the command to deactivate it immediately after execution.
+
+This mechanism is turned off by default. Set ``PYCONSOLE_TRACE`` before SALOME start to activate it:
+
+.. code-block:: console
+
+ export PYCONSOLE_TRACE=1
+
+If the tracing is on while the script is running we can see functions enter and return calls
+with ``>>`` and ``<<`` marks followed by line number and function name. Tracing function prints only calls to functions
+defined in the current script to prevent printing of thousands of lines for builtin functions in some cases.
+
+Example of a script with defined functions:
+
+.. code-block:: python
+ :linenos:
+
+ #!/usr/bin/env python
+
+ import time
+
+ def sum(x, y):
+ time.sleep(3)
+ return x + y
+
+ def sub(x, y):
+ time.sleep(3)
+ return x - y
+
+ def prod(x, y):
+ time.sleep(3)
+ return x * y
+
+ def div(x, y):
+ time.sleep(3)
+ return x / y
+
+ x = 5
+ y = 2
+
+ sum(x, y)
+ sub(x, y)
+ prod(x, y)
+ div(x, y)
+
+And output in the Python console:
+
+.. code-block:: console
+
+ >>> sys.setprofile(lambda frame, event, arg: print('>>', frame.f_lineno, ': ', frame.f_code.co_name) if event == 'call' and frame.f_code.co_filename == '/home/function_calls.py' and frame.f_code.co_name != '<module>' else print('<<', frame.f_lineno, ': ', frame.f_code.co_name) if event == 'return' and frame.f_code.co_filename == '/home/function_calls.py' and frame.f_code.co_name != '<module>' else None); exec(compile(open('/home/function_calls.py', 'rb').read(), '/home/function_calls.py', 'exec')); sys.setprofile(None);
+ >> 5 : sum
+ << 7 : sum
+ >> 9 : sub
+ << 11 : sub
+ >> 13 : prod
+ << 15 : prod
+ >> 17 : div
+ << 19 : div
+ >>>
+
#include <stdio.h>
#include <stdarg.h>
+#include <iostream>
/*!
Traces output to log-file.
{
SUIT_Tools::alignWidget( src, ref, Qt::AlignCenter );
}
+
+/*!
+ Add tracing code to given python command if it was activated by PYCONSOLE_TRACE env variable.
+ Immediately return if PYCONSOLE_TRACE wasn't set.
+*/
+void SUIT_Tools::addTraceToPythonCommand(const QString& fileName, QString& command)
+{
+ auto isPythonTraceEnabled = []() -> bool
+ {
+ const char* envVar = std::getenv("PYCONSOLE_TRACE");
+
+ if (envVar && (envVar[0] != '\0'))
+ {
+ try
+ {
+ const long long numValue = std::stoll(envVar);
+ return numValue > 0;
+ }
+ catch(const std::exception& e)
+ {
+ std::cerr << e.what() << '\n';
+ }
+ }
+
+ return false;
+ };
+
+ static const bool isActivated = isPythonTraceEnabled();
+ if (!isActivated)
+ {
+ return;
+ }
+
+ // Using sys.setprofile() instead of sys.settrace() because we don't need any other events except of 'call' and 'return'.
+ // Another reason: the trace function for sys.settrace() must return itself, so we can't use it properly with lambda.
+ command = QString("sys.setprofile(lambda frame, event, arg: "
+ "print('>>', frame.f_lineno, ': ', frame.f_code.co_name) if event == 'call' and frame.f_code.co_filename == '%1' and frame.f_code.co_name != '<module>' else "
+ "print('<<', frame.f_lineno, ': ', frame.f_code.co_name) if event == 'return' and frame.f_code.co_filename == '%1' and frame.f_code.co_name != '<module>' else "
+ "None); "
+ "%2; "
+ "sys.setprofile(None); ").
+ arg(fileName).
+ arg(command);
+}
static QString fontToString( const QFont& font );
static void centerWidget( QWidget* src, const QWidget* ref );
+
+ static void addTraceToPythonCommand(const QString& fileName, QString& command);
};
#endif
/*!SLOT. Create new study and load script*/
void SalomeApp_Application::onNewWithScript()
{
- QStringList filtersList;
- filtersList.append(tr("PYTHON_FILES_FILTER"));
- filtersList.append(tr("ALL_FILES_FILTER"));
-
- QString anInitialPath = "";
- if ( SUIT_FileDlg::getLastVisitedPath().isEmpty() )
- anInitialPath = QDir::currentPath();
-
- QString aFile = SUIT_FileDlg::getFileName( desktop(), anInitialPath, filtersList, tr( "TOT_DESK_FILE_LOAD_SCRIPT" ), true, true );
-
- if ( !aFile.isEmpty() )
- {
- onNewDoc();
-
-#ifndef DISABLE_PYCONSOLE
- QString command = QString("exec(open(\"%1\", \"rb\").read())").arg(aFile);
- PyConsole_Console* pyConsole = pythonConsole();
- PropertyMgr propm( this, "IsLoadedScript", true );
- if ( pyConsole )
- pyConsole->exec( command );
-#endif
- }
+ execScript(true);
}
return;
}
- QStringList filtersList;
- filtersList.append(tr("PYTHON_FILES_FILTER"));
- filtersList.append(tr("ALL_FILES_FILTER"));
-
- QString anInitialPath = "";
- if ( SUIT_FileDlg::getLastVisitedPath().isEmpty() )
- anInitialPath = QDir::currentPath();
-
- QString aFile = SUIT_FileDlg::getFileName( desktop(), anInitialPath, filtersList, tr( "TOT_DESK_FILE_LOAD_SCRIPT" ), true, true );
-
- if ( !aFile.isEmpty() )
- {
-#ifndef DISABLE_PYCONSOLE
- QString command = QString("exec(compile(open('%1', 'rb').read(), '%1', 'exec'))").arg(aFile);
- PyConsole_Console* pyConsole = pythonConsole();
- PropertyMgr propm( this, "IsLoadedScript", true );
- if ( pyConsole )
- pyConsole->exec(command);
-#endif
- }
+ execScript(false);
}
/*!Private SLOT. On save GUI state.*/
#endif // DISABLE_PYCONSOLE
+/*
+ Opens a file dialog to choose a python script.
+*/
+QString SalomeApp_Application::getScriptFileName()
+{
+ QStringList filtersList;
+ filtersList.append(tr("PYTHON_FILES_FILTER"));
+ filtersList.append(tr("ALL_FILES_FILTER"));
+
+ const QString anInitialPath =
+ SUIT_FileDlg::getLastVisitedPath().isEmpty() ? QDir::currentPath() : "";
+
+ return SUIT_FileDlg::getFileName(desktop(), anInitialPath, filtersList, tr("TOT_DESK_FILE_LOAD_SCRIPT"), true, true);
+}
+
+/*
+ Execute script in python console.
+*/
+void SalomeApp_Application::execScript(bool isNewDoc)
+{
+ const QString aFile = getScriptFileName();
+ if (aFile.isEmpty())
+ {
+ return;
+ }
+
+ if (isNewDoc)
+ {
+ onNewDoc();
+ }
+
+#ifndef DISABLE_PYCONSOLE
+ PyConsole_Console* pyConsole = pythonConsole();
+ PropertyMgr propm(this, "IsLoadedScript", true);
+ if (pyConsole)
+ {
+ QString command = QString("exec(compile(open('%1', 'rb').read(), '%1', 'exec'))").arg(aFile);
+ SUIT_Tools::addTraceToPythonCommand(aFile, command);
+
+ pyConsole->exec(command);
+ }
+#endif
+}
+
void SalomeApp_Application::ensureShaperIsActivated()
{
SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>(activeStudy());
void createExtraActions();
void ensureShaperIsActivated();
+ QString getScriptFileName();
+ void execScript(bool isNewDoc);
+
private:
#ifndef DISABLE_PYCONSOLE
QPointer<SalomeApp_NoteBook> myNoteBook; // Notebook instance
lay->addWidget( myEditor );
// force synchronous mode
+ // By default we set PyConsole_Editor::myIsSync = false, so we have async as default mode.
+ // But in previous versions of Salome we used sync mode by default because of our testing setup
+ // where VTK must run in the main thread (see assert(vtkGarbageCollectorIsMainThread())).
+ // So, here for compatibility reasons we consider undefined PYTHON_CONSOLE_SYNC as true as well.
+ // One who needs to run scripts in async mode to see intermediate output during execution,
+ // must expicitly set PYTHON_CONSOLE_SYNC=0 before Salome start.
QString synchronous = qgetenv( "PYTHON_CONSOLE_SYNC" );
- if ( !synchronous.isEmpty() && synchronous.toInt() > 0 )
+ if (synchronous.isEmpty() || synchronous.toInt() > 0)
setIsSync( true );
// create actions
myCmdInHistory = -1;
myEventLoop = 0;
myShowBanner = true;
- myIsSync = true;
+ myIsSync = false;
myIsSuppressOutput = false;
myMultiLinePaste = false;
myAutoCompletion = false;