diff options
author | Campbell Barton <ideasman42@gmail.com> | 2009-04-30 12:01:31 +0400 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2009-04-30 12:01:31 +0400 |
commit | b5b24ee521866637871e7555d4294c0936a0664d (patch) | |
tree | 37634051fe6b01bea895adad4899f75db47187b6 | |
parent | 939290b59c52bdd84b9f20083d4dd76ada293ee8 (diff) |
BGE Python sys.path for the blenderplayer and blender
sys.path is the search path for python modules. This is useful so people making games can put all their scripts in a folder and be sure they will always load into the BGE.
for each blend file a scripts directory is added to the path
/home/me/foo.blend
will look for modules in...
/home/me/scripts/*.py
It could also default to look for modules in the same directory as the blend file but I think this is messy.
Added a note in the tooltip about //scripts so its not such a hidden feature.
This works by storing the original sys.path, then adding the paths for the blendfile and all its libs,
when a new blendfile is loaded, the original sys.path is restored before adding the blendfiles paths again so the sys.path wont get junk in it.
One problem with this - when using linked libs the module names must be unique else it will load the wrong module for one of the controllers.
also fixed 2 bugs
- sys.path in the blenderplayer was growing by 1 for every file load in blenderplayer
- the relative path (gp_GamePythonPath), wasnt being set when loading files in the blenderlayer (as I wrongly said in the last commit).
-rw-r--r-- | source/blender/src/buttons_logic.c | 6 | ||||
-rw-r--r-- | source/gameengine/GamePlayer/ghost/GPG_ghost.cpp | 2 | ||||
-rw-r--r-- | source/gameengine/Ketsji/KX_PythonInit.cpp | 120 |
3 files changed, 123 insertions, 5 deletions
diff --git a/source/blender/src/buttons_logic.c b/source/blender/src/buttons_logic.c index 168726c9dab..54b48de716f 100644 --- a/source/blender/src/buttons_logic.c +++ b/source/blender/src/buttons_logic.c @@ -1580,12 +1580,12 @@ static short draw_controllerbuttons(bController *cont, uiBlock *block, short xco uiBlockBeginAlign(block); - uiDefButI(block, MENU, B_REDR, "Execution Method%t|Script%x0|Module%x1", xco+24,yco-24, 66, 19, &pc->mode, 0, 0, 0, 0, "Python script type (textblock or module)"); + uiDefButI(block, MENU, B_REDR, "Execution Method%t|Script%x0|Module%x1", xco+24,yco-24, 66, 19, &pc->mode, 0, 0, 0, 0, "Python script type (textblock or module - faster)"); if(pc->mode==0) uiDefIDPoinBut(block, test_scriptpoin_but, ID_SCRIPT, 1, "", xco+90,yco-24,width-90, 19, &pc->text, "Blender textblock to run as a script"); else { - uiDefBut(block, TEX, 1, "", xco+90,yco-24,(width-90)-25, 19, pc->module, 0, 63, 0, 0, "Module name and function to run eg \"someModule.main\""); - uiDefButBitI(block, TOG, CONT_PY_DEBUG, B_REDR, "D", (xco+width)-25, yco-24, 19, 19, &pc->flag, 0, 0, 0, 0, "Continuously reload the module from disk for editing external modules without restrting, (slow)"); + uiDefBut(block, TEX, 1, "", xco+90,yco-24,(width-90)-25, 19, pc->module, 0, 63, 0, 0, "Module name and function to run eg \"someModule.main\", internal texts and //scripts/*.py on the filesystem can be used"); + uiDefButBitI(block, TOG, CONT_PY_DEBUG, B_REDR, "D", (xco+width)-25, yco-24, 19, 19, &pc->flag, 0, 0, 0, 0, "Continuously reload the module from disk for editing external modules without restrting"); } uiBlockEndAlign(block); diff --git a/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp b/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp index 64e70ce37c3..7c47d2353a6 100644 --- a/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp +++ b/source/gameengine/GamePlayer/ghost/GPG_ghost.cpp @@ -704,10 +704,10 @@ int main(int argc, char** argv) BLI_strncpy(pathname, maggie->name, sizeof(pathname)); BLI_strncpy(G.sce, maggie->name, sizeof(G.sce)); + setGamePythonPath(G.sce); if (firstTimeRunning) { - setGamePythonPath(G.sce); firstTimeRunning = false; if (fullScreen) diff --git a/source/gameengine/Ketsji/KX_PythonInit.cpp b/source/gameengine/Ketsji/KX_PythonInit.cpp index 22960eaed2c..b17d4042bb6 100644 --- a/source/gameengine/Ketsji/KX_PythonInit.cpp +++ b/source/gameengine/Ketsji/KX_PythonInit.cpp @@ -80,6 +80,10 @@ #include "KX_PythonInitTypes.h" +/* we only need this to get a list of libraries from the main struct */ +#include "DNA_ID.h" +#include "BKE_main.h" + extern "C" { #include "Mathutils.h" // Blender.Mathutils module copied here so the blenderlayer can use. #include "bpy_internal_import.h" /* from the blender python api, but we want to import text too! */ @@ -110,6 +114,7 @@ static KX_Scene* gp_KetsjiScene = NULL; static KX_KetsjiEngine* gp_KetsjiEngine = NULL; static RAS_IRasterizer* gp_Rasterizer = NULL; static char gp_GamePythonPath[FILE_MAXDIR + FILE_MAXFILE] = ""; +static PyObject *gp_OrigPythonSysPath= NULL; void KX_RasterizerDrawDebugLine(const MT_Vector3& from,const MT_Vector3& to,const MT_Vector3& color) { @@ -1522,11 +1527,111 @@ void setSandbox(TPythonSecurityLevel level) } } +/* Explanation of + * + * - backupPySysPath() : stores sys.path in gp_OrigPythonSysPath + * - initPySysPath(main) : initializes the blendfile and library paths + * - restorePySysPath() : restores sys.path from gp_OrigPythonSysPath + * + * These exist so the //scripts folder can always be used to import modules from. + * the reason we need a few functions for this is that python is not only used by the game engine + * so we cant just add to sys.path all the time, it would leave pythons state in a mess. + * It would also be incorrect since loading blend files for new levels etc would alwasy add to sys.path + * + * To play nice with blenders python, the sys.path is backed up and the current blendfile along + * with all its lib paths are added to the sys path. + * When loading a new blendfile, the original sys.path is restored and the new paths are added over the top. + */ + +/** + * So we can have external modules mixed with our blend files. + */ +static void backupPySysPath(void) +{ + PyObject *sys_path= PySys_GetObject("path"); /* should never fail */ + + /* just incase its set */ + Py_XDECREF(gp_OrigPythonSysPath); + gp_OrigPythonSysPath= NULL; + + gp_OrigPythonSysPath = PyList_GetSlice(sys_path, 0, INT_MAX); /* copy the list */ +} + +/* for initPySysPath only, + * takes a blend path and adds a scripts dir from it + * + * "/home/me/foo.blend" -> "/home/me/scripts" + */ +static void initPySysPath__append(PyObject *sys_path, char *filename) +{ + PyObject *item; + char expanded[FILE_MAXDIR + FILE_MAXFILE] = "//"; + + BLI_convertstringcode(expanded, filename); + BLI_join_dirfile(expanded, expanded, "scripts"); /* double checked and using the dir twice is safe */ + + item= PyString_FromString(expanded); + + if(PySequence_Index(sys_path, item) == -1) { + PyList_Insert(sys_path, 0, item); + } + + Py_DECREF(item); +} +static void initPySysPath(Main *maggie) +{ + PyObject *sys_path= PySys_GetObject("path"); /* should never fail */ + + if (gp_OrigPythonSysPath==NULL) { + /* backup */ + backupPySysPath(); + } + else { + /* get the original sys path when the BGE started */ + PyList_SetSlice(sys_path, 0, INT_MAX, gp_OrigPythonSysPath); + } + + Library *lib= (Library *)maggie->library.first; + + while(lib) { + initPySysPath__append(sys_path, lib->name); + lib= (Library *)lib->id.next; + } + + initPySysPath__append(sys_path, gp_GamePythonPath); + +// fprintf(stderr, "\nNew Path: %d ", PyList_Size(sys_path)); +// PyObject_Print(sys_path, stderr, 0); +} + +static void restorePySysPath(void) +{ + if (gp_OrigPythonSysPath==NULL) + return; + + PyObject *sys_path= PySys_GetObject("path"); /* should never fail */ + + PyList_SetSlice(sys_path, 0, INT_MAX, gp_OrigPythonSysPath); + Py_DECREF(gp_OrigPythonSysPath); + gp_OrigPythonSysPath= NULL; + +// fprintf(stderr, "\nRestore Path: %d ", PyList_Size(sys_path)); +// PyObject_Print(sys_path, stderr, 0); +} + /** * Python is not initialised. */ PyObject* initGamePlayerPythonScripting(const STR_String& progname, TPythonSecurityLevel level, Main *maggie, int argc, char** argv) { + /* Yet another gotcha in the py api + * Cant run PySys_SetArgv more then once because this adds the + * binary dir to the sys.path each time. + * Id have thaught python being totally restarted would make this ok but + * somehow it remembers the sys.path - Campbell + */ + static bool first_time = true; + #if (PY_VERSION_HEX < 0x03000000) STR_String pname = progname; Py_SetProgramName(pname.Ptr()); @@ -1536,7 +1641,7 @@ PyObject* initGamePlayerPythonScripting(const STR_String& progname, TPythonSecur Py_Initialize(); #if (PY_VERSION_HEX < 0x03000000) - if(argv) /* browser plugins dont currently set this */ + if(argv && first_time) /* browser plugins dont currently set this */ PySys_SetArgv(argc, argv); #endif //importBlenderModules() @@ -1546,6 +1651,10 @@ PyObject* initGamePlayerPythonScripting(const STR_String& progname, TPythonSecur bpy_import_main_set(maggie); + initPySysPath(maggie); + + first_time = false; + PyObject* moduleobj = PyImport_AddModule("__main__"); return PyModule_GetDict(moduleobj); } @@ -1553,10 +1662,16 @@ PyObject* initGamePlayerPythonScripting(const STR_String& progname, TPythonSecur void exitGamePlayerPythonScripting() { //clearGameModules(); // were closing python anyway + + /* since python restarts we cant let the python backup of the sys.path hang around in a global pointer */ + restorePySysPath(); /* get back the original sys.path and clear the backup */ + Py_Finalize(); bpy_import_main_set(NULL); } + + /** * Python is already initialized. */ @@ -1574,6 +1689,8 @@ PyObject* initGamePythonScripting(const STR_String& progname, TPythonSecurityLev bpy_import_main_set(maggie); + initPySysPath(maggie); + /* clear user defined modules that may contain data from the last run */ clearGameModules(); @@ -1619,6 +1736,7 @@ static void clearGameModules() void exitGamePythonScripting() { clearGameModules(); + restorePySysPath(); /* get back the original sys.path and clear the backup */ bpy_import_main_set(NULL); } |