Skip to content

Commit 3e78367

Browse files
committed
Merge branch 'devel' into feature-ire
2 parents 8a5e8f4 + 48f8c2b commit 3e78367

File tree

6 files changed

+134
-85
lines changed

6 files changed

+134
-85
lines changed

cisstInteractive/code/ireFramework.cpp

Lines changed: 94 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class PyTextCtrlHook {
8888
static PyObject* PythonEventClass; // the event object to pass to PostEvent
8989
static PyObject* PythonWindow; // the window that will handle the event
9090
static PyMethodDef Methods[];
91+
#if (PY_MAJOR_VERSION >= 3)
92+
static PyModuleDef ModuleDef;
93+
#endif
9194
static cmnCallbackStreambuf<char> *Streambuf;
9295
static cmnLogMask Mask;
9396

@@ -108,7 +111,7 @@ class PyTextCtrlHook {
108111
static void PrintLog(const char * str, int len);
109112

110113
// Initialize the Python module
111-
static void InitModule(const char * name);
114+
static PyObject *InitModule();
112115

113116
// Cleanup
114117
static void Cleanup();
@@ -201,7 +204,6 @@ void PyTextCtrlHook::SetupStreambuf(void)
201204
// len: the length of the string
202205
void PyTextCtrlHook::PrintLog(const char * str, int len)
203206
{
204-
#if (PY_MAJOR_VERSION == 2)
205207
PyObject* result;
206208

207209
// If PythonFunc is set, we know that PythonEventClass is also set
@@ -211,32 +213,38 @@ void PyTextCtrlHook::PrintLog(const char * str, int len)
211213
// This function is defined in Python 2.3 or greater.
212214
PyGILState_STATE gstate = PyGILState_Ensure();
213215

214-
PyObject* event = PyObject_CallObject(PythonEventClass, NULL);
216+
// Create an instance of the event class and set the msg attribute
217+
PyObject *empty = Py_BuildValue("()");
218+
PyObject *kwargs = Py_BuildValue("{s:s}", "msg", str);
219+
PyObject* event = PyObject_Call(PythonEventClass, empty, kwargs);
215220
CMN_ASSERT(event);
221+
Py_DECREF(empty);
222+
Py_DECREF(kwargs);
216223

217-
// Convert the C string to a Python string
218-
PyObject* pystr = Py_BuildValue("s", str);
219-
// Store the string as the "msg" attribute in the event object
220-
CMN_ASSERT(PyObject_SetAttrString(event, "msg", pystr) == 0);
221-
// Decrement "pystr" reference count (PyObject_SetAttrString incremented it).
222-
Py_DECREF(pystr);
223224
// Build the args for the Python function
224225
PyObject* args = Py_BuildValue("OO", PythonWindow, event);
225226
// Decrement "event" reference count (Py_BuildValue incremented it)
226227
Py_DECREF(event);
227228
// Call the callback function
229+
#if (PY_MAJOR_VERSION == 2)
228230
if (PyCFunction_Check(PythonFunc)) {
229231
// If it is a (wrapped) C function, call it directly.
230232
try {
231233
result = PyCFunction_Call(PythonFunc, args, 0);
234+
result = PyObject_CallObject(PythonFunc, args);
232235
}
233236
catch(...) {
234237
std::cout << "PyCFunction_Call exception" << std::endl;
235238
}
236239
}
237-
else
240+
else {
238241
// Call the Python function (via the interpreter)
239-
result = PyEval_CallObject(PythonFunc, args);
242+
result = PyObject_CallObject(PythonFunc, args);
243+
}
244+
#else
245+
// Call the Python function (via the interpreter)
246+
result = PyObject_CallObject(PythonFunc, args);
247+
#endif
240248
// Decrement reference count
241249
Py_DECREF(args);
242250

@@ -251,7 +259,6 @@ void PyTextCtrlHook::PrintLog(const char * str, int len)
251259
// Release Python global interpreter lock
252260
PyGILState_Release(gstate);
253261
}
254-
#endif
255262
}
256263

257264
// Cleanup: remove logger channel, delete Streambuf object,
@@ -295,11 +302,31 @@ PyMethodDef PyTextCtrlHook::Methods[] = {
295302
{ NULL, NULL, 0, NULL }
296303
};
297304

298-
void PyTextCtrlHook::InitModule(const char * name)
305+
#if (PY_MAJOR_VERSION >= 3)
306+
PyModuleDef PyTextCtrlHook::ModuleDef = {
307+
PyModuleDef_HEAD_INIT,
308+
"ireLogger",
309+
"ireLogger documentation",
310+
-1,
311+
PyTextCtrlHook::Methods,
312+
NULL,
313+
NULL,
314+
NULL,
315+
NULL
316+
};
317+
#endif
318+
319+
PyObject *PyTextCtrlHook::InitModule()
299320
{
321+
PyObject *pModule;
300322
#if (PY_MAJOR_VERSION == 2)
301-
Py_InitModule(name, Methods);
323+
pModule = Py_InitModule("ireLogger", Methods);
324+
#else
325+
pModule = PyModule_Create(&ModuleDef);
302326
#endif
327+
if (pModule == NULL)
328+
CMN_LOG_INIT_ERROR << "PyTextCtrlHook::InitModule failed" << std::endl;
329+
return pModule;
303330
}
304331

305332
// ****************************************************************************
@@ -333,8 +360,8 @@ ireFramework* ireFramework::Instance(void) {
333360
//
334361
void ireFramework::InitShellInstance(void)
335362
{
336-
if (IsInitialized()) {
337-
CMN_LOG_INIT_WARNING << "ireFramework already initialized" << std::endl;
363+
if (IRE_State != IRE_NOT_CONSTRUCTED) {
364+
CMN_LOG_INIT_WARNING << "ireFramework already constructed" << std::endl;
338365
return;
339366
}
340367
#if (CISST_OS == CISST_LINUX)
@@ -353,22 +380,7 @@ void ireFramework::InitShellInstance(void)
353380
<< ": " << msg << std::endl;
354381
}
355382
#endif
356-
Py_Initialize();
357-
#if (PY_MAJOR_VERSION < 3) || (PY_MINOR_VERSION < 7)
358-
// PyEval_InitThreads was deprecated in Python 3.7 (it is no longer needed)
359-
PyEval_InitThreads();
360-
#endif
361-
#if (CISST_OS == CISST_LINUX)
362-
#if (PY_MAJOR_VERSION == 2)
363-
// TODO: check if this is needed for Python3
364-
// For Linux, change dlopenflags to avoid swig::stop_iterator exceptions
365-
PyGILState_STATE gstate = PyGILState_Ensure();
366-
PyThreadState *threadState = PyThreadState_Get();
367-
threadState->interp->dlopenflags |= RTLD_GLOBAL;
368-
PyGILState_Release(gstate);
369-
#endif
370-
#endif
371-
IRE_State = IRE_INITIALIZED;
383+
IRE_State = IRE_CONSTRUCTED;
372384
}
373385

374386
// ****************************************************************************
@@ -400,27 +412,66 @@ void ireFramework::FinalizeShellInstance(void)
400412
// of any PyObject s that use hard references.
401413
//
402414
void ireFramework::LaunchIREShellInstance(const char * startup, bool newPythonThread, bool useIPython,
403-
bool useStreambuf) {
404-
//start python
405-
const char * python_args[] = { "", startup };
406-
407-
if (IRE_State != IRE_INITIALIZED) {
415+
bool useStreambuf)
416+
{
417+
if (IRE_State != IRE_CONSTRUCTED) {
408418
CMN_LOG_INIT_ERROR << "LaunchIREShellInstance: IRE state is " << IRE_State << "." << std::endl;
409419
cmnThrow(std::runtime_error("LaunchIREShellInstance: invalid IRE state."));
410420
}
411-
IRE_State = IRE_LAUNCHED;
421+
412422
NewPythonThread = newPythonThread;
413423

414424
if (pInstance)
415425
Py_DECREF(pInstance);
416426
pInstance = 0;
417427

418428
#if (PY_MAJOR_VERSION == 2)
419-
// Initialize ireLogger module, which is used for the cmnLogger output window
420-
PyTextCtrlHook::InitModule("ireLogger");
429+
Py_Initialize();
430+
// Set python_args as argv.
431+
// Note that currently, startup script run separately for IPython.
432+
const char * python_args[] = { "", startup };
421433
PySys_SetArgv(2, const_cast<char **>(python_args));
434+
// Initialize ireLogger module, which is used for the cmnLogger output window
435+
PyTextCtrlHook::InitModule();
436+
#else
437+
// Following setting of python_args appears to be necessary when using Python 3
438+
// with wxPython. Note that currently, startup script run separately for IPython.
439+
const char * python_args[] = { "", "IRE", startup };
440+
PyConfig config;
441+
PyConfig_InitPythonConfig(&config);
442+
PyConfig_SetBytesArgv(&config, 3, const_cast<char **>(python_args));
443+
// Initialize ireLogger module, which is used for the cmnLogger output window
444+
if (PyImport_AppendInittab("ireLogger", PyTextCtrlHook::InitModule) == -1)
445+
CMN_LOG_INIT_WARNING << "LaunchIREShellInstance: failed to import ireLogger" << std::endl;
446+
PyStatus status;
447+
status = Py_InitializeFromConfig(&config);
448+
PyConfig_Clear(&config);
449+
if (PyStatus_IsError(status)) {
450+
CMN_LOG_INIT_ERROR << "LaunchIREShellInstance: init error: " << status.err_msg << std::endl;
451+
cmnThrow(std::runtime_error("LaunchIREShellInstance: init error"));
452+
}
453+
else if (PyStatus_IsExit(status)) {
454+
CMN_LOG_INIT_ERROR << "LaunchIREShellInstance: init exit: " << status.err_msg << std::endl;
455+
cmnThrow(std::runtime_error("LaunchIREShellInstance: init exit"));
456+
}
457+
#endif
458+
#if (PY_MAJOR_VERSION < 3) || (PY_MINOR_VERSION < 7)
459+
// PyEval_InitThreads was deprecated in Python 3.7 (it is no longer needed)
460+
PyEval_InitThreads();
461+
#endif
462+
#if (CISST_OS == CISST_LINUX)
463+
#if (PY_MAJOR_VERSION == 2)
464+
// TODO: check if this is needed for Python3
465+
// For Linux, change dlopenflags to avoid swig::stop_iterator exceptions
466+
PyGILState_STATE gstate = PyGILState_Ensure();
467+
PyThreadState *threadState = PyThreadState_Get();
468+
threadState->interp->dlopenflags |= RTLD_GLOBAL;
469+
PyGILState_Release(gstate);
470+
#endif
422471
#endif
423472

473+
IRE_State = IRE_LAUNCHED;
474+
424475
// Get global dictionary (pModule and pDict are borrowed references)
425476
PyObject *pModule = PyImport_AddModule("__main__");
426477
PyObject *pDict = PyModule_GetDict(pModule);
@@ -432,6 +483,7 @@ void ireFramework::LaunchIREShellInstance(const char * startup, bool newPythonTh
432483

433484
char launchString[128];
434485
if (useIPython) {
486+
// Could instead add "argv=[%s]" to launchString, where %s is startup string
435487
PyRun_SimpleString(startup);
436488
// Following code is for very old versions of IPython (possibly prior to 1.0)
437489
// PyRun_SimpleString("from IPython.Shell import IPShellEmbed\n");
@@ -526,12 +578,8 @@ bool ireFramework::IsInitialized()
526578

527579
void ireFramework::Reset()
528580
{
529-
if (IRE_State == IRE_FINISHED) {
530-
if (!IsInitialized())
531-
InitShell();
532-
else
533-
IRE_State = IRE_INITIALIZED;
534-
}
581+
if (IRE_State == IRE_FINISHED)
582+
IRE_State = IRE_CONSTRUCTED;
535583
}
536584

537585
void ireFramework::UnblockThreads()

cisstInteractive/code/ireTask.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
Author(s): Peter Kazanzides
77
Created on: 2010-12-10
88
9-
(C) Copyright 2010 Johns Hopkins University (JHU), All Rights Reserved.
9+
(C) Copyright 2010-2025 Johns Hopkins University (JHU), All Rights Reserved.
1010
1111
--- begin cisst license - do not edit ---
1212
@@ -52,7 +52,7 @@ void ireTask::Initialize(void)
5252
required->AddEventHandlerWrite(&ireTask::Log, this,
5353
mtsManagerComponentBase::EventNames::PrintLog);
5454
}
55-
SetInitializationDelay(30.0); // Allow up to 30 seconds for it to start
55+
SetInitializationDelay(10.0); // Allow up to 10 seconds for it to start
5656
}
5757

5858
ireTask::~ireTask()
@@ -74,8 +74,9 @@ void ireTask::Startup(void)
7474
<< GetName() << " = cisstCommon.cmnObjectRegister.FindObject('" << GetName() << "'); "
7575
<< StartupCommands;
7676
try {
77-
// Don't use cmnCallbackStreambuf because we will use system-wide log
78-
ireFramework::LaunchIREShell(startup.str().c_str(), true, (Shell == IRE_IPYTHON), false);
77+
// Set last parameter true to use cmnCallbackStreambuf (standard CMN_LOG)
78+
// Set last parameter false if using system-wide log (threaded logging)
79+
ireFramework::LaunchIREShell(startup.str().c_str(), true, (Shell == IRE_IPYTHON), true);
7980
}
8081
catch (...) {
8182
if (Shell == IRE_IPYTHON)

cisstInteractive/code/irepy/ireMain.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -599,17 +599,15 @@ def GetTypeString(self, obj):
599599
#-------------------------------------------------
600600

601601
def SaveHistoryToFile(self):
602-
# PK TODO: pickle not yet working in Python3
603-
if sys.version_info.major == 2:
604-
cmdlist = self.CommandHistoryListCtrl.GetAllItems()
605-
f = open(self.HISTORY_FILENAME, 'w')
606-
pickle.dump(cmdlist, f)
602+
cmdlist = self.CommandHistoryListCtrl.GetAllItems()
603+
f = open(self.HISTORY_FILENAME, 'wb')
604+
pickle.dump(cmdlist, f)
607605

608606
def LoadHistoryFromFile(self, fn=HISTORY_FILENAME):
609607
Data = []
610608
if os.path.isfile(fn):
611609
try:
612-
Data = pickle.load(open(fn))
610+
Data = pickle.load(open(fn, 'rb'))
613611
except Exception as error:
614612
msgdlg = wx.MessageDialog(self, str(error), "Load History", wx.OK | wx.ICON_ERROR)
615613
msgdlg.ShowModal()
@@ -693,13 +691,13 @@ def TruncateHistoryFile(self):
693691
# write the command file up to the selected line into the named file
694692
# fetch any existing list first and append to it
695693
try:
696-
archlist = pickle.load(open(fn))
694+
archlist = pickle.load(open(fn), 'rb')
697695
except IOError:
698696
archlist = []
699697
n=0
700698
for n in range(choice):
701699
archlist.append( cmdlist[n] )
702-
pickle.dump( archlist, open(fn, 'w'))
700+
pickle.dump( archlist, open(fn, 'wb'))
703701
text = "Your command history was archived to " + fn
704702
msgdlg = wx.MessageDialog(self, text, title, wx.OK | wx.ICON_INFORMATION)
705703
msgdlg.ShowModal()
@@ -836,7 +834,7 @@ def OnViewLogger(self, event):
836834
def OnLoadWorkspace(self, event):
837835
Filepath = py.editor.openSingle(directory='',wildcard='IRE Workspace (*.ws)|*.ws|All Files (*.*)|*.*').path
838836
if Filepath:
839-
File = open(Filepath)
837+
File = open(Filepath, 'rb')
840838
LoadWorkspaceFile(self.Shell.interp.locals, File)
841839
File.close()
842840
self.CheckScopeVariables()
@@ -848,7 +846,7 @@ def OnSaveWorkspace(self, event):
848846
(name, ext) = os.path.splitext(Filepath)
849847
if not ext:
850848
Filepath += '.ws'
851-
File = open(Filepath,'w')
849+
File = open(Filepath,'wb')
852850
SaveWorkspaceToFile(self.Shell.interp.locals, File)
853851
File.close()
854852

cisstInteractive/code/irepy/ireWorkspace.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def main():
7676
v1 = v2 = None
7777
print('v1 = ', v1, '\nv2 = ', v2)
7878
LoadFile(open('workspace'))
79-
Workspace = pickle.load(open('workspace'))
79+
Workspace = pickle.load(open('workspace', 'rb'))
8080
for Variable in Workspace:
8181
exec(Variable + " = Workspace['" + Variable + "']")
8282
print('v1 = ', v1, '\nv2 = ', v2)

cisstInteractive/code/makeIrepy.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,13 @@ def main():
6262
sys.exit(1)
6363

6464
# compile the source files
65-
for f in glob.glob1(src,'*.py'):
66-
py_compile.compile(os.path.join(src,f), os.path.join(dest,f+'c'))
65+
for fpath in glob.glob(os.path.join(src,'*.py')):
66+
fname = os.path.basename(fpath)
67+
py_compile.compile(fpath, os.path.join(dest,fname+'c'))
6768

6869
#Now copy the images over.
6970
#This can be directly handled by CMake
7071
#shutil.copytree(os.path.join(src,'images', os.path.join(dest, 'images')))
7172

7273
if __name__ == "__main__":
7374
main()
74-
75-

0 commit comments

Comments
 (0)